From 3f99e3dff5a072002f7fc5aed48666f7e8fbc762 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Mon, 27 Apr 2026 20:58:42 +0200 Subject: [PATCH 01/30] feat: refactor connectivity modules and add DNS zone support Co-authored-by: Copilot --- examples/01-standalone/README.md | 1 - examples/01-standalone/main.tf | 2 + examples/01-standalone/outputs.tf | 4 +- examples/01-standalone/terraform.tfvars | 5 -- examples/01-standalone/variables.tf | 8 +-- examples/02-hub-spoke/README.md | 1 - examples/02-hub-spoke/main.tf | 38 ++++++----- examples/02-hub-spoke/outputs.tf | 16 ++--- examples/02-hub-spoke/terraform.tfvars | 45 ++++++------- examples/02-hub-spoke/variables.tf | 59 +++++++++++------ modules/connectivity-global/1-network-area.tf | 34 ---------- .../1-project.tf | 11 +--- modules/connectivity-global/2-dns-zones.tf | 12 ++++ modules/connectivity-global/outputs.tf | 21 ++++++- modules/connectivity-global/variables.tf | 55 +++++++++++----- .../connectivity-regional/1-network-area.tf | 23 +++++++ .../2-external-network.tf | 20 +++--- .../3-internal-network.tf | 10 ++- modules/connectivity-regional/4-firewall.tf | 20 +++--- modules/connectivity-regional/outputs.tf | 20 ++---- modules/connectivity-regional/variables.tf | 63 ++++++++++++------- 21 files changed, 269 insertions(+), 199 deletions(-) delete mode 100644 modules/connectivity-global/1-network-area.tf rename modules/{connectivity-regional => connectivity-global}/1-project.tf (67%) create mode 100644 modules/connectivity-global/2-dns-zones.tf create mode 100644 modules/connectivity-regional/1-network-area.tf diff --git a/examples/01-standalone/README.md b/examples/01-standalone/README.md index 4a690a2..38fedfb 100644 --- a/examples/01-standalone/README.md +++ b/examples/01-standalone/README.md @@ -36,7 +36,6 @@ No resources. | [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes | | [organization\_owners](#input\_organization\_owners) | List of organization owners. | `list(string)` | `[]` | no | | [owner\_email](#input\_owner\_email) | Email address of the owner. Required for STACKIT resource manager. | `string` | n/a | yes | -| [platform\_admins](#input\_platform\_admins) | List of platform administrators. | `list(string)` | `[]` | no | | [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | diff --git a/examples/01-standalone/main.tf b/examples/01-standalone/main.tf index 09d9f04..d6c7885 100644 --- a/examples/01-standalone/main.tf +++ b/examples/01-standalone/main.tf @@ -50,6 +50,7 @@ module "management" { module "devops" { source = "../../modules/devops" + count = var.devops_enabled ? 1 : 0 owner_email = var.owner_email naming_pattern = "${var.company_code}-pltfm-devops-prod" @@ -64,6 +65,7 @@ module "devops" { module "sandboxes" { source = "../../modules/sandboxes" + count = length(var.sandboxes) > 0 ? 1 : 0 naming_prefix = "${var.company_code}-sbx" parent_container_id = module.governance.folder_container_ids["sandboxes"] diff --git a/examples/01-standalone/outputs.tf b/examples/01-standalone/outputs.tf index 1b8d42d..f820abc 100644 --- a/examples/01-standalone/outputs.tf +++ b/examples/01-standalone/outputs.tf @@ -9,7 +9,7 @@ output "governance_folder_ids" { output "devops_project_id" { description = "The project ID of the DevOps project." - value = module.devops.project_id + value = length(module.devops) > 0 ? module.devops[0].project_id : null } output "management_project_id" { @@ -19,7 +19,7 @@ output "management_project_id" { output "sandbox_projects" { description = "The created sandbox projects." - value = module.sandboxes.projects + value = length(module.sandboxes) > 0 ? module.sandboxes[0].projects : {} } output "landing_zone_projects" { diff --git a/examples/01-standalone/terraform.tfvars b/examples/01-standalone/terraform.tfvars index 04e0844..857f5e1 100644 --- a/examples/01-standalone/terraform.tfvars +++ b/examples/01-standalone/terraform.tfvars @@ -27,11 +27,6 @@ organization_auditors = [ "auditor@example.com" ] -# Users with admin access to the Platform folder (DevOps, Management) -platform_admins = [ - "platform-admin@example.com" -] - # Sandbox projects for experimentation / PoCs sandboxes = [ { diff --git a/examples/01-standalone/variables.tf b/examples/01-standalone/variables.tf index 5432b70..d310265 100644 --- a/examples/01-standalone/variables.tf +++ b/examples/01-standalone/variables.tf @@ -46,10 +46,10 @@ variable "organization_auditors" { default = [] } -variable "platform_admins" { - type = list(string) - description = "List of platform administrators." - default = [] +variable "devops_enabled" { + type = bool + description = "Whether to deploy the DevOps module (Git repository project)." + default = true } variable "sandboxes" { diff --git a/examples/02-hub-spoke/README.md b/examples/02-hub-spoke/README.md index fd286fa..449608d 100644 --- a/examples/02-hub-spoke/README.md +++ b/examples/02-hub-spoke/README.md @@ -45,7 +45,6 @@ No resources. | [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes | | [organization\_owners](#input\_organization\_owners) | List of organization owners. | `list(string)` | `[]` | no | | [owner\_email](#input\_owner\_email) | Email address of the owner. Required for STACKIT resource manager. | `string` | n/a | yes | -| [platform\_admins](#input\_platform\_admins) | List of platform administrators. | `list(string)` | `[]` | no | | [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | diff --git a/examples/02-hub-spoke/main.tf b/examples/02-hub-spoke/main.tf index eae1e4d..b275a23 100644 --- a/examples/02-hub-spoke/main.tf +++ b/examples/02-hub-spoke/main.tf @@ -56,9 +56,11 @@ module "management" { module "connectivity_global" { source = "../../modules/connectivity-global" - organization_id = var.organization_id - labels = var.labels - network_areas = var.network_areas + owner_email = var.owner_email + naming_pattern = "${var.company_code}-pltfm-net-prod" + parent_container_id = module.governance.folder_container_ids["platform"] + organization_id = var.organization_id + labels = var.labels } ############################# @@ -68,18 +70,20 @@ module "connectivity_global" { module "connectivity_regional" { source = "../../modules/connectivity-regional" - owner_email = var.owner_email - naming_pattern = "${var.company_code}-pltfm-hub-prod" - parent_container_id = module.governance.folder_container_ids["platform"] - organization_id = var.organization_id - network_area_id = module.connectivity_global.network_area_ids[var.connectivity_regional_network_area] - labels = var.labels - firewall_zone = var.firewall_zone - firewall_flavor = var.firewall_flavor - vnet_range = var.connectivity_vnet_range - firewall_ip = var.firewall_ip - - # for multiple regions define alias + project_id = module.connectivity_global.project_id + organization_id = var.organization_id + network_area_name = var.network_area_name + network_ranges = var.network_ranges + transfer_network_range = var.transfer_network_range + min_prefix_length = var.min_prefix_length + max_prefix_length = var.max_prefix_length + default_prefix_length = var.default_prefix_length + labels = var.labels + firewall_enabled = var.firewall_enabled + firewall_zone = var.firewall_zone + firewall_flavor = var.firewall_flavor + vnet_range = var.connectivity_vnet_range + firewall_ip = var.firewall_ip } ############ @@ -88,6 +92,7 @@ module "connectivity_regional" { module "devops" { source = "../../modules/devops" + count = var.devops_enabled ? 1 : 0 owner_email = var.owner_email naming_pattern = "${var.company_code}-pltfm-devops-prod" @@ -102,6 +107,7 @@ module "devops" { module "sandboxes" { source = "../../modules/sandboxes" + count = length(var.sandboxes) > 0 ? 1 : 0 naming_prefix = "${var.company_code}-sbx" parent_container_id = module.governance.folder_container_ids["sandboxes"] @@ -119,7 +125,7 @@ module "landing_zone" { organization_id = var.organization_id parent_container_id = each.value.corporate ? module.governance.folder_container_ids["landing_zones_corporate"] : module.governance.folder_container_ids["landing_zones_public"] naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" - network_area_id = each.value.corporate ? module.connectivity_global.network_area_ids[var.connectivity_regional_network_area] : null + network_area_id = each.value.corporate ? module.connectivity_regional.network_area_id : null owner_email = each.value.owner_email labels = var.labels role_assignments = each.value.role_assignments diff --git a/examples/02-hub-spoke/outputs.tf b/examples/02-hub-spoke/outputs.tf index a4e8b41..f0af52c 100644 --- a/examples/02-hub-spoke/outputs.tf +++ b/examples/02-hub-spoke/outputs.tf @@ -9,7 +9,7 @@ output "governance_folder_ids" { output "devops_project_id" { description = "The project ID of the DevOps project." - value = module.devops.project_id + value = length(module.devops) > 0 ? module.devops[0].project_id : null } output "management_project_id" { @@ -17,14 +17,14 @@ output "management_project_id" { value = module.management.project_id } -output "connectivity_global_network_area_ids" { - description = "Map of network area names to their IDs." - value = module.connectivity_global.network_area_ids +output "connectivity_regional_network_area_id" { + description = "The network area ID created by the regional module." + value = module.connectivity_regional.network_area_id } -output "connectivity_regional_project_id" { - description = "The project ID of the regional connectivity project." - value = module.connectivity_regional.project_id +output "connectivity_global_project_id" { + description = "The project ID of the connectivity project." + value = module.connectivity_global.project_id } output "connectivity_regional_firewall_public_ip" { @@ -34,7 +34,7 @@ output "connectivity_regional_firewall_public_ip" { output "sandbox_projects" { description = "The created sandbox projects." - value = module.sandboxes.projects + value = length(module.sandboxes) > 0 ? module.sandboxes[0].projects : {} } output "landing_zone_projects" { diff --git a/examples/02-hub-spoke/terraform.tfvars b/examples/02-hub-spoke/terraform.tfvars index caccdc9..5a1f6b2 100644 --- a/examples/02-hub-spoke/terraform.tfvars +++ b/examples/02-hub-spoke/terraform.tfvars @@ -28,40 +28,33 @@ organization_auditors = [ "auditor@example.com" ] -# Users with admin access to the Platform folder (DevOps, Management, Connectivity) -platform_admins = [ - "platform-admin@example.com" -] - ########################### ## CONNECTIVITY - GLOBAL ## ########################### -# Network areas define the overall IP address space available for projects -# Each area gets a transfer network (for inter-project routing) and one or more ranges -network_areas = [ - { - name = "corporate" - # IP ranges that will be sliced into per-project subnets - network_ranges = [ - { prefix = "10.1.0.0/16" }, - { prefix = "10.2.0.0/16" } - ] - # Transfer network used for routing between projects in this area - transfer_network_range = "10.255.0.0/24" - # Controls the subnet sizes assigned to individual projects - min_prefix_length = 24 - max_prefix_length = 28 - default_prefix_length = 25 - } -] - ############################# ## CONNECTIVITY - REGIONAL ## ############################# -# Must match a name from the network_areas list above -connectivity_regional_network_area = "corporate" +# Name of the network area for this region +network_area_name = "corporate" + +# IP ranges that will be sliced into per-project subnets +network_ranges = [ + { prefix = "10.1.0.0/16" }, + { prefix = "10.2.0.0/16" } +] + +# Transfer network used for routing between projects in this area +transfer_network_range = "10.255.0.0/24" + +# Controls the subnet sizes assigned to individual projects +min_prefix_length = 24 +max_prefix_length = 28 +default_prefix_length = 25 + +# Set to false to skip firewall deployment (network area and routing still created) +# firewall_enabled = false # Availability zone for the firewall VM firewall_zone = "eu01-m" diff --git a/examples/02-hub-spoke/variables.tf b/examples/02-hub-spoke/variables.tf index abc18e3..00db338 100644 --- a/examples/02-hub-spoke/variables.tf +++ b/examples/02-hub-spoke/variables.tf @@ -46,36 +46,57 @@ variable "organization_auditors" { default = [] } -variable "platform_admins" { - type = list(string) - description = "List of platform administrators." - default = [] +variable "devops_enabled" { + type = bool + description = "Whether to deploy the DevOps module (Git repository project)." + default = true } ########################### ## CONNECTIVITY - GLOBAL ## ########################### -variable "network_areas" { - type = list(object({ - name = string - network_ranges = list(object({ prefix = string })) - transfer_network_range = string - max_prefix_length = optional(number, 28) - min_prefix_length = optional(number, 24) - default_prefix_length = optional(number, 28) - default_nameservers = optional(list(string), null) - })) - description = "List of network areas to create with their IP ranges and configuration." -} - ############################# ## CONNECTIVITY - REGIONAL ## ############################# -variable "connectivity_regional_network_area" { +variable "network_area_name" { type = string - description = "Name key of the network area (from network_areas) to use for the regional connectivity project." + description = "Name of the network area for this region." +} + +variable "network_ranges" { + type = list(object({ prefix = string })) + description = "IP ranges that will be sliced into per-project subnets." +} + +variable "transfer_network_range" { + type = string + description = "Transfer network CIDR used for routing between projects in this area." +} + +variable "max_prefix_length" { + type = number + description = "Maximum prefix length for subnets assigned to projects." + default = 28 +} + +variable "min_prefix_length" { + type = number + description = "Minimum prefix length for subnets assigned to projects." + default = 24 +} + +variable "default_prefix_length" { + type = number + description = "Default prefix length for subnets assigned to projects." + default = 28 +} + +variable "firewall_enabled" { + type = bool + description = "Whether to deploy the pfSense firewall. Set to false for connectivity without a firewall (network area and routing are still created)." + default = true } variable "firewall_zone" { diff --git a/modules/connectivity-global/1-network-area.tf b/modules/connectivity-global/1-network-area.tf deleted file mode 100644 index db4103f..0000000 --- a/modules/connectivity-global/1-network-area.tf +++ /dev/null @@ -1,34 +0,0 @@ -################## -## NETWORK AREA ## -################## - -locals { - project_labels = merge( - var.network_area_id != null ? { "networkArea" = var.network_area_id } : {}, - var.labels - ) -} - -resource "stackit_network_area" "this" { - for_each = { for na in var.network_areas : na.name => na } - - organization_id = var.organization_id - name = each.key - labels = merge(local.project_labels, { "preview/routingtables" = "true" }) -} - -resource "stackit_network_area_region" "this" { - for_each = { for na in var.network_areas : na.name => na } - - organization_id = var.organization_id - network_area_id = stackit_network_area.this[each.key].network_area_id - - ipv4 = { - network_ranges = each.value.network_ranges - transfer_network = each.value.transfer_network_range - max_prefix_length = each.value.max_prefix_length - min_prefix_length = each.value.min_prefix_length - default_prefix_length = each.value.default_prefix_length - default_nameservers = each.value.default_nameservers - } -} \ No newline at end of file diff --git a/modules/connectivity-regional/1-project.tf b/modules/connectivity-global/1-project.tf similarity index 67% rename from modules/connectivity-regional/1-project.tf rename to modules/connectivity-global/1-project.tf index d16e004..8e6a60f 100644 --- a/modules/connectivity-regional/1-project.tf +++ b/modules/connectivity-global/1-project.tf @@ -2,18 +2,11 @@ ## PROJECT ## ############# -locals { - project_labels = merge( - var.network_area_id != null ? { "networkArea" = var.network_area_id } : {}, - var.labels - ) -} - resource "stackit_resourcemanager_project" "this" { parent_container_id = var.parent_container_id name = var.project_name != null ? var.project_name : var.naming_pattern owner_email = var.owner_email - labels = length(local.project_labels) > 0 ? local.project_labels : null # provider bug: empty map becomes null after apply + labels = length(var.labels) > 0 ? var.labels : null # provider bug: empty map becomes null after apply } resource "stackit_authorization_project_role_assignment" "this" { @@ -22,4 +15,4 @@ resource "stackit_authorization_project_role_assignment" "this" { resource_id = stackit_resourcemanager_project.this.project_id role = each.value.role subject = each.value.subject -} +} \ No newline at end of file diff --git a/modules/connectivity-global/2-dns-zones.tf b/modules/connectivity-global/2-dns-zones.tf new file mode 100644 index 0000000..9352823 --- /dev/null +++ b/modules/connectivity-global/2-dns-zones.tf @@ -0,0 +1,12 @@ +resource "stackit_dns_zone" "this" { + for_each = var.dns_zones + + project_id = stackit_resourcemanager_project.this.project_id + name = each.value.name + dns_name = each.value.dns_name + contact_email = each.value.contact_email + type = each.value.type + acl = each.value.acl + description = each.value.description + default_ttl = each.value.default_ttl +} \ No newline at end of file diff --git a/modules/connectivity-global/outputs.tf b/modules/connectivity-global/outputs.tf index 30b15f5..636c061 100644 --- a/modules/connectivity-global/outputs.tf +++ b/modules/connectivity-global/outputs.tf @@ -1,4 +1,19 @@ -output "network_area_ids" { - description = "Map of network area names to their IDs." - value = { for name, area in stackit_network_area.this : name => area.network_area_id } +output "project_container_id" { + description = "The container ID of the created STACKIT project." + value = stackit_resourcemanager_project.this.container_id +} + +output "project_id" { + description = "The project ID of the created STACKIT project." + value = stackit_resourcemanager_project.this.project_id +} + +output "project_name" { + description = "The name of the created STACKIT project." + value = stackit_resourcemanager_project.this.name +} + +output "dns_zone_ids" { + description = "Map of DNS zone keys to their zone IDs" + value = { for k, z in stackit_dns_zone.this : k => z.zone_id } } \ No newline at end of file diff --git a/modules/connectivity-global/variables.tf b/modules/connectivity-global/variables.tf index 3549cdc..dc765ae 100644 --- a/modules/connectivity-global/variables.tf +++ b/modules/connectivity-global/variables.tf @@ -1,29 +1,54 @@ variable "labels" { type = map(string) - description = "Additional labels to apply to all folders." + description = "Additional labels to apply to all resources." default = {} } -variable "network_area_id" { +variable "naming_pattern" { type = string - description = "Network Area ID to deploy resources into. Required if network is enabled." + description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-net-prod\"." +} + +variable "organization_id" { + type = string + description = "Container ID of the root folder or organization under which the company folder will be created." +} + +variable "owner_email" { + type = string + description = "Email address of the owner for the project. Required for STACKIT resource manager." +} + +variable "parent_container_id" { + type = string + description = "Parent container ID (folder or organization) where the project will be created." +} + +variable "project_name" { + type = string + description = "Name of the STACKIT project to create. Falls back to naming_pattern if not set." default = null } -variable "network_areas" { +variable "role_assignments" { type = list(object({ - name = string - network_ranges = list(object({ prefix = string })) - transfer_network_range = string - max_prefix_length = optional(number, 28) - min_prefix_length = optional(number, 24) - default_prefix_length = optional(number, 28) - default_nameservers = optional(list(string), ["1.0.0.1", "1.1.1.1"]) + role = string + subject = string })) - description = "List of network areas to create, each with its own name, ranges, and configuration." + description = "List of role assignments for the project. Subject can be a user email or service account email." + default = [] } -variable "organization_id" { - type = string - description = "Container ID of the root folder or organization under which the company folder will be created." +variable "dns_zones" { + type = map(object({ + name = string + dns_name = string + contact_email = optional(string, null) + type = optional(string, "primary") + acl = optional(string, null) + description = optional(string, null) + default_ttl = optional(number, 3600) + })) + description = "Map of DNS zone keys to DNS zone configuration." + default = {} } \ No newline at end of file diff --git a/modules/connectivity-regional/1-network-area.tf b/modules/connectivity-regional/1-network-area.tf new file mode 100644 index 0000000..3ce9f15 --- /dev/null +++ b/modules/connectivity-regional/1-network-area.tf @@ -0,0 +1,23 @@ +################## +## NETWORK AREA ## +################## + +resource "stackit_network_area" "this" { + organization_id = var.organization_id + name = var.network_area_name + labels = merge(var.labels, { "preview/routingtables" = "true" }) +} + +resource "stackit_network_area_region" "this" { + organization_id = var.organization_id + network_area_id = stackit_network_area.this.network_area_id + + ipv4 = { + network_ranges = var.network_ranges + transfer_network = var.transfer_network_range + max_prefix_length = var.max_prefix_length + min_prefix_length = var.min_prefix_length + default_prefix_length = var.default_prefix_length + default_nameservers = var.default_nameservers + } +} diff --git a/modules/connectivity-regional/2-external-network.tf b/modules/connectivity-regional/2-external-network.tf index 08a92fa..290afad 100644 --- a/modules/connectivity-regional/2-external-network.tf +++ b/modules/connectivity-regional/2-external-network.tf @@ -4,14 +4,14 @@ resource "stackit_routing_table" "wan" { organization_id = var.organization_id - network_area_id = var.network_area_id + network_area_id = stackit_network_area.this.network_area_id name = "wan" system_routes = true } resource "stackit_routing_table_route" "wan" { organization_id = var.organization_id - network_area_id = var.network_area_id + network_area_id = stackit_network_area.this.network_area_id routing_table_id = stackit_routing_table.wan.routing_table_id destination = { @@ -29,20 +29,26 @@ resource "stackit_routing_table_route" "wan" { ############# resource "stackit_network" "wan" { - project_id = stackit_resourcemanager_project.this.project_id + count = var.firewall_enabled ? 1 : 0 + + project_id = var.project_id name = "wan_network" routing_table_id = stackit_routing_table.wan.routing_table_id routed = true } resource "stackit_network_interface" "wan" { + count = var.firewall_enabled ? 1 : 0 + name = "vtnet0_wan" - project_id = stackit_resourcemanager_project.this.project_id - network_id = stackit_network.wan.network_id + project_id = var.project_id + network_id = stackit_network.wan[0].network_id security = false } resource "stackit_public_ip" "wan-ip" { - project_id = stackit_resourcemanager_project.this.project_id - network_interface_id = stackit_network_interface.wan.network_interface_id + count = var.firewall_enabled ? 1 : 0 + + project_id = var.project_id + network_interface_id = stackit_network_interface.wan[0].network_interface_id } \ No newline at end of file diff --git a/modules/connectivity-regional/3-internal-network.tf b/modules/connectivity-regional/3-internal-network.tf index 254076d..88f6cea 100644 --- a/modules/connectivity-regional/3-internal-network.tf +++ b/modules/connectivity-regional/3-internal-network.tf @@ -3,14 +3,18 @@ ############# resource "stackit_network" "lan" { - project_id = stackit_resourcemanager_project.this.project_id + count = var.firewall_enabled ? 1 : 0 + + project_id = var.project_id name = "lan_network" routed = true } resource "stackit_network_interface" "lan" { + count = var.firewall_enabled ? 1 : 0 + name = "vtnet1_lan" - project_id = stackit_resourcemanager_project.this.project_id - network_id = stackit_network.lan.network_id + project_id = var.project_id + network_id = stackit_network.lan[0].network_id security = false } \ No newline at end of file diff --git a/modules/connectivity-regional/4-firewall.tf b/modules/connectivity-regional/4-firewall.tf index cc043fc..c2efea4 100644 --- a/modules/connectivity-regional/4-firewall.tf +++ b/modules/connectivity-regional/4-firewall.tf @@ -13,7 +13,9 @@ # } resource "stackit_image" "pfsense_image" { - project_id = stackit_resourcemanager_project.this.project_id + count = var.firewall_enabled ? 1 : 0 + + project_id = var.project_id name = "pfsense-2.7.2-amd64-image" local_file_path = "./pfsense.qcow2" disk_format = "qcow2" @@ -31,13 +33,15 @@ resource "stackit_image" "pfsense_image" { ############ resource "stackit_volume" "pfsense_vol" { - project_id = stackit_resourcemanager_project.this.project_id + count = var.firewall_enabled ? 1 : 0 + + project_id = var.project_id name = "pfsense-2.7.2-root" availability_zone = var.firewall_zone size = 16 performance_class = "storage_premium_perf4" source = { - id = stackit_image.pfsense_image.image_id + id = stackit_image.pfsense_image[0].image_id type = "image" } } @@ -48,17 +52,19 @@ resource "stackit_volume" "pfsense_vol" { # after rollout: https://docs.stackit.cloud/products/quick-deployments/pfsense-firewall/tutorials/configure-pfsense/ resource "stackit_server" "pfsense_Server" { - project_id = stackit_resourcemanager_project.this.project_id + count = var.firewall_enabled ? 1 : 0 + + project_id = var.project_id name = "pfSense" boot_volume = { source_type = "volume" - source_id = stackit_volume.pfsense_vol.volume_id + source_id = stackit_volume.pfsense_vol[0].volume_id } availability_zone = var.firewall_zone machine_type = var.firewall_flavor network_interfaces = [ - stackit_network_interface.wan.network_interface_id, # vtnet0 = WAN - stackit_network_interface.lan.network_interface_id # vtnet1 = LAN + stackit_network_interface.wan[0].network_interface_id, # vtnet0 = WAN + stackit_network_interface.lan[0].network_interface_id # vtnet1 = LAN ] } \ No newline at end of file diff --git a/modules/connectivity-regional/outputs.tf b/modules/connectivity-regional/outputs.tf index b27b2ce..507ccd1 100644 --- a/modules/connectivity-regional/outputs.tf +++ b/modules/connectivity-regional/outputs.tf @@ -1,24 +1,14 @@ output "firewall_public_ip" { description = "The public IP address of the pfSense firewall WAN interface." - value = stackit_public_ip.wan-ip.ip + value = var.firewall_enabled ? stackit_public_ip.wan-ip[0].ip : null } output "firewall_next_hop_ip" { description = "The IP address to be used as next hop for the default route in the landing zones (pfSense WAN IP)." - value = stackit_network_interface.lan.ipv4 + value = var.firewall_enabled ? stackit_network_interface.lan[0].ipv4 : null } -output "project_container_id" { - description = "The container ID of the created STACKIT project." - value = stackit_resourcemanager_project.this.container_id -} - -output "project_id" { - description = "The project ID of the created STACKIT project." - value = stackit_resourcemanager_project.this.project_id -} - -output "project_name" { - description = "The name of the created STACKIT project." - value = stackit_resourcemanager_project.this.name +output "network_area_id" { + description = "The ID of the created network area." + value = stackit_network_area.this.network_area_id } diff --git a/modules/connectivity-regional/variables.tf b/modules/connectivity-regional/variables.tf index e1cf112..a4fa64c 100644 --- a/modules/connectivity-regional/variables.tf +++ b/modules/connectivity-regional/variables.tf @@ -1,3 +1,9 @@ +variable "firewall_enabled" { + type = bool + description = "Whether to deploy the pfSense firewall and associated WAN/LAN networks. Set to false for connectivity without a firewall." + default = true +} + variable "firewall_flavor" { type = string description = "Firewall VM Flavor" @@ -23,48 +29,57 @@ variable "firewall_zone" { variable "labels" { type = map(string) - description = "Additional labels to apply to all folders." + description = "Additional labels to apply to all resources." default = {} } -variable "naming_pattern" { +variable "network_area_name" { type = string - description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-net-prod\"." + description = "Name of the network area to create for this region." } -variable "network_area_id" { - type = string - description = "Network Area ID to deploy resources into. Required if network is enabled." +variable "network_ranges" { + type = list(object({ prefix = string })) + description = "IP ranges that will be sliced into per-project subnets." } -variable "organization_id" { +variable "transfer_network_range" { type = string - description = "Organization ID, required for network area route configuration." + description = "Transfer network CIDR used for routing between projects in this area." } -variable "owner_email" { - type = string - description = "Email address of the owner for the folders. Required for STACKIT resource manager." +variable "max_prefix_length" { + type = number + description = "Maximum prefix length for subnets assigned to projects." + default = 28 } -variable "parent_container_id" { - type = string - description = "Parent container ID (folder or organization) where the project will be created." +variable "min_prefix_length" { + type = number + description = "Minimum prefix length for subnets assigned to projects." + default = 24 } -variable "project_name" { +variable "default_prefix_length" { + type = number + description = "Default prefix length for subnets assigned to projects." + default = 28 +} + +variable "default_nameservers" { + type = list(string) + description = "Default nameservers for the network area." + default = ["1.0.0.1", "1.1.1.1"] +} + +variable "organization_id" { type = string - description = "Name of the STACKIT project to create." - default = null + description = "Organization ID, required for network area and route configuration." } -variable "role_assignments" { - type = list(object({ - role = string - subject = string - })) - description = "List of role assignments for the project. Subject can be a user email or service account email." - default = [] +variable "project_id" { + type = string + description = "Project ID of the connectivity project (created by connectivity-global)." } variable "vnet_range" { From dc414ff2acc085ca0fd6b207bebc124f998bdf24 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Mon, 27 Apr 2026 21:25:21 +0200 Subject: [PATCH 02/30] feat: add DNS zone management to hub-spoke example and update related modules Co-authored-by: Copilot --- examples/02-hub-spoke/main.tf | 24 ++++++++++++---------- examples/02-hub-spoke/outputs.tf | 5 +++-- examples/02-hub-spoke/terraform.tfvars | 16 ++++++--------- examples/02-hub-spoke/variables.tf | 21 +++++++++++-------- modules/connectivity-global/2-dns-zones.tf | 2 +- modules/connectivity-global/outputs.tf | 5 +++++ modules/connectivity-global/variables.tf | 4 ++-- modules/landing-zone/7-dns-zone.tf | 13 ++++++++++++ modules/landing-zone/outputs.tf | 10 +++++++++ modules/landing-zone/variables.tf | 6 ++++++ 10 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 modules/landing-zone/7-dns-zone.tf diff --git a/examples/02-hub-spoke/main.tf b/examples/02-hub-spoke/main.tf index b275a23..f91c658 100644 --- a/examples/02-hub-spoke/main.tf +++ b/examples/02-hub-spoke/main.tf @@ -61,6 +61,7 @@ module "connectivity_global" { parent_container_id = module.governance.folder_container_ids["platform"] organization_id = var.organization_id labels = var.labels + dns_zones = var.dns_zones } ############################# @@ -72,7 +73,7 @@ module "connectivity_regional" { project_id = module.connectivity_global.project_id organization_id = var.organization_id - network_area_name = var.network_area_name + network_area_name = "${var.company_code}-pltfm-hub-prod" network_ranges = var.network_ranges transfer_network_range = var.transfer_network_range min_prefix_length = var.min_prefix_length @@ -122,14 +123,15 @@ module "landing_zone" { source = "../../modules/landing-zone" for_each = var.landing_zones - organization_id = var.organization_id - parent_container_id = each.value.corporate ? module.governance.folder_container_ids["landing_zones_corporate"] : module.governance.folder_container_ids["landing_zones_public"] - naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" - network_area_id = each.value.corporate ? module.connectivity_regional.network_area_id : null - owner_email = each.value.owner_email - labels = var.labels - role_assignments = each.value.role_assignments - network_prefix_length = each.value.network_prefix_length - custom_roles = each.value.custom_roles - firewall_next_hop_ip = module.connectivity_regional.firewall_next_hop_ip + organization_id = var.organization_id + parent_container_id = each.value.corporate ? module.governance.folder_container_ids["landing_zones_corporate"] : module.governance.folder_container_ids["landing_zones_public"] + naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" + dns_zone_name = "${each.value.project_code}.${each.value.env}.${var.region}.${values(module.connectivity_global.dns_zone_dns_names)[0]}" + network_area_id = each.value.corporate ? module.connectivity_regional.network_area_id : null + owner_email = each.value.owner_email + labels = var.labels + role_assignments = each.value.role_assignments + network_prefix_length = each.value.network_prefix_length + custom_roles = each.value.custom_roles + firewall_next_hop_ip = module.connectivity_regional.firewall_next_hop_ip } diff --git a/examples/02-hub-spoke/outputs.tf b/examples/02-hub-spoke/outputs.tf index f0af52c..2688ad8 100644 --- a/examples/02-hub-spoke/outputs.tf +++ b/examples/02-hub-spoke/outputs.tf @@ -41,8 +41,9 @@ output "landing_zone_projects" { description = "Map of landing zone project IDs." value = { for k, v in module.landing_zone : k => { - project_id = v.project_id - project_name = v.project_name + project_id = v.project_id + project_name = v.project_name + dns_zone_name = v.dns_zone_dns_name } } } diff --git a/examples/02-hub-spoke/terraform.tfvars b/examples/02-hub-spoke/terraform.tfvars index 5a1f6b2..0de74f7 100644 --- a/examples/02-hub-spoke/terraform.tfvars +++ b/examples/02-hub-spoke/terraform.tfvars @@ -28,16 +28,12 @@ organization_auditors = [ "auditor@example.com" ] -########################### -## CONNECTIVITY - GLOBAL ## -########################### - -############################# -## CONNECTIVITY - REGIONAL ## -############################# - -# Name of the network area for this region -network_area_name = "corporate" +# DNS zones managed in the connectivity project +dns_zones = { + "prodyna" = { + dns_name = "prodyna.stackit.run" + } +} # IP ranges that will be sliced into per-project subnets network_ranges = [ diff --git a/examples/02-hub-spoke/variables.tf b/examples/02-hub-spoke/variables.tf index 00db338..431910a 100644 --- a/examples/02-hub-spoke/variables.tf +++ b/examples/02-hub-spoke/variables.tf @@ -52,19 +52,24 @@ variable "devops_enabled" { default = true } -########################### -## CONNECTIVITY - GLOBAL ## -########################### +variable "dns_zones" { + type = map(object({ + dns_name = string + name = optional(string, null) + contact_email = optional(string, null) + type = optional(string, "primary") + acl = optional(string, null) + description = optional(string, null) + default_ttl = optional(number, 3600) + })) + description = "Map of DNS zone keys to DNS zone configuration. Name defaults to dns_name if not set." + default = {} +} ############################# ## CONNECTIVITY - REGIONAL ## ############################# -variable "network_area_name" { - type = string - description = "Name of the network area for this region." -} - variable "network_ranges" { type = list(object({ prefix = string })) description = "IP ranges that will be sliced into per-project subnets." diff --git a/modules/connectivity-global/2-dns-zones.tf b/modules/connectivity-global/2-dns-zones.tf index 9352823..48fafa5 100644 --- a/modules/connectivity-global/2-dns-zones.tf +++ b/modules/connectivity-global/2-dns-zones.tf @@ -2,7 +2,7 @@ resource "stackit_dns_zone" "this" { for_each = var.dns_zones project_id = stackit_resourcemanager_project.this.project_id - name = each.value.name + name = each.value.name != null ? each.value.name : each.value.dns_name dns_name = each.value.dns_name contact_email = each.value.contact_email type = each.value.type diff --git a/modules/connectivity-global/outputs.tf b/modules/connectivity-global/outputs.tf index 636c061..8f9d063 100644 --- a/modules/connectivity-global/outputs.tf +++ b/modules/connectivity-global/outputs.tf @@ -16,4 +16,9 @@ output "project_name" { output "dns_zone_ids" { description = "Map of DNS zone keys to their zone IDs" value = { for k, z in stackit_dns_zone.this : k => z.zone_id } +} + +output "dns_zone_dns_names" { + description = "Map of DNS zone keys to their DNS names" + value = { for k, z in stackit_dns_zone.this : k => z.dns_name } } \ No newline at end of file diff --git a/modules/connectivity-global/variables.tf b/modules/connectivity-global/variables.tf index dc765ae..b964f98 100644 --- a/modules/connectivity-global/variables.tf +++ b/modules/connectivity-global/variables.tf @@ -41,14 +41,14 @@ variable "role_assignments" { variable "dns_zones" { type = map(object({ - name = string dns_name = string + name = optional(string, null) contact_email = optional(string, null) type = optional(string, "primary") acl = optional(string, null) description = optional(string, null) default_ttl = optional(number, 3600) })) - description = "Map of DNS zone keys to DNS zone configuration." + description = "Map of DNS zone keys to DNS zone configuration. Name defaults to dns_name if not set." default = {} } \ No newline at end of file diff --git a/modules/landing-zone/7-dns-zone.tf b/modules/landing-zone/7-dns-zone.tf new file mode 100644 index 0000000..f5c6bbe --- /dev/null +++ b/modules/landing-zone/7-dns-zone.tf @@ -0,0 +1,13 @@ +############## +## DNS ZONE ## +############## + +resource "stackit_dns_zone" "this" { + count = var.dns_zone_name != null ? 1 : 0 + + project_id = stackit_resourcemanager_project.this.project_id + name = var.dns_zone_name + dns_name = var.dns_zone_name + type = "primary" + default_ttl = 3600 +} diff --git a/modules/landing-zone/outputs.tf b/modules/landing-zone/outputs.tf index d88bbcb..5ed1872 100644 --- a/modules/landing-zone/outputs.tf +++ b/modules/landing-zone/outputs.tf @@ -11,4 +11,14 @@ output "project_id" { output "project_name" { description = "The name of the created STACKIT project." value = stackit_resourcemanager_project.this.name +} + +output "dns_zone_dns_name" { + description = "The DNS name of the landing zone's child DNS zone." + value = var.dns_zone_name != null ? stackit_dns_zone.this[0].dns_name : null +} + +output "dns_zone_id" { + description = "The ID of the landing zone's child DNS zone." + value = var.dns_zone_name != null ? stackit_dns_zone.this[0].zone_id : null } \ No newline at end of file diff --git a/modules/landing-zone/variables.tf b/modules/landing-zone/variables.tf index 30d04df..28300a8 100644 --- a/modules/landing-zone/variables.tf +++ b/modules/landing-zone/variables.tf @@ -70,4 +70,10 @@ variable "ipv4_nameservers" { type = list(string) description = "List of IPv4 nameservers for the network." default = null +} + +variable "dns_zone_name" { + type = string + description = "Full DNS zone domain name for this landing zone. Set to null to skip DNS zone creation." + default = null } \ No newline at end of file From e56052c7ebe7fd14fc923727507cf33f87629438 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 28 Apr 2026 10:31:41 +0200 Subject: [PATCH 03/30] feat: update naming patterns and refactor network area and firewall configurations --- CODEOWNERS | 2 +- examples/01-standalone/backend.tf | 14 ++ examples/02-hub-spoke/backend.tf | 14 ++ examples/02-hub-spoke/main.tf | 50 ++--- examples/02-hub-spoke/outputs.tf | 12 +- examples/02-hub-spoke/terraform.tfvars | 171 +++++++++--------- examples/02-hub-spoke/variables.tf | 78 +++----- modules/connectivity-global/1-project.tf | 18 -- modules/connectivity-global/2-dns-zones.tf | 12 -- modules/connectivity-global/README.md | 42 ----- modules/connectivity-global/terraform.tf | 10 - .../connectivity-regional/1-network-area.tf | 23 --- .../3-internal-network.tf | 20 -- modules/connectivity-regional/outputs.tf | 14 -- modules/connectivity-regional/pfsense.qcow2 | 1 - modules/connectivity-regional/variables.tf | 89 --------- modules/connectivity/1-network-area.tf | 23 +++ modules/connectivity/2-project.tf | 43 +++++ .../3-external-network.tf} | 22 ++- modules/connectivity/4-internal-network.tf | 22 +++ .../5-firewall.tf} | 18 +- .../README.md | 2 +- .../outputs.tf | 35 +++- .../terraform.tf | 4 + .../variables.tf | 68 +++++-- modules/devops/README.md | 2 +- modules/devops/variables.tf | 2 +- modules/landing-zone/3-network.tf | 6 +- modules/landing-zone/README.md | 2 +- modules/landing-zone/variables.tf | 10 +- modules/management/README.md | 2 +- modules/management/variables.tf | 2 +- modules/sandboxes/README.md | 2 +- 33 files changed, 367 insertions(+), 468 deletions(-) create mode 100644 examples/01-standalone/backend.tf create mode 100644 examples/02-hub-spoke/backend.tf delete mode 100644 modules/connectivity-global/1-project.tf delete mode 100644 modules/connectivity-global/2-dns-zones.tf delete mode 100644 modules/connectivity-global/README.md delete mode 100644 modules/connectivity-global/terraform.tf delete mode 100644 modules/connectivity-regional/1-network-area.tf delete mode 100644 modules/connectivity-regional/3-internal-network.tf delete mode 100644 modules/connectivity-regional/outputs.tf delete mode 100644 modules/connectivity-regional/pfsense.qcow2 delete mode 100644 modules/connectivity-regional/variables.tf create mode 100644 modules/connectivity/1-network-area.tf create mode 100644 modules/connectivity/2-project.tf rename modules/{connectivity-regional/2-external-network.tf => connectivity/3-external-network.tf} (64%) create mode 100644 modules/connectivity/4-internal-network.tf rename modules/{connectivity-regional/4-firewall.tf => connectivity/5-firewall.tf} (76%) rename modules/{connectivity-regional => connectivity}/README.md (99%) rename modules/{connectivity-global => connectivity}/outputs.tf (57%) rename modules/{connectivity-regional => connectivity}/terraform.tf (66%) rename modules/{connectivity-global => connectivity}/variables.tf (50%) diff --git a/CODEOWNERS b/CODEOWNERS index 7bf8e5c..477770f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ -* david.wenzel@stackit.cloud @mahauber +* @dweezl @mahauber @simpe00 docs/* @lweberru scripts/* @lweberru \ No newline at end of file diff --git a/examples/01-standalone/backend.tf b/examples/01-standalone/backend.tf new file mode 100644 index 0000000..c9266ff --- /dev/null +++ b/examples/01-standalone/backend.tf @@ -0,0 +1,14 @@ +# terraform { +# backend "s3" { +# bucket = "" +# endpoints = { +# s3 = "https://object.storage.eu01.onstackit.cloud" +# } +# key = "terraform.tfstate" +# region = "eu01" +# skip_credentials_validation = true +# skip_region_validation = true +# skip_requesting_account_id = true +# skip_s3_checksum = true +# } +# } diff --git a/examples/02-hub-spoke/backend.tf b/examples/02-hub-spoke/backend.tf new file mode 100644 index 0000000..c9266ff --- /dev/null +++ b/examples/02-hub-spoke/backend.tf @@ -0,0 +1,14 @@ +# terraform { +# backend "s3" { +# bucket = "" +# endpoints = { +# s3 = "https://object.storage.eu01.onstackit.cloud" +# } +# key = "terraform.tfstate" +# region = "eu01" +# skip_credentials_validation = true +# skip_region_validation = true +# skip_requesting_account_id = true +# skip_s3_checksum = true +# } +# } diff --git a/examples/02-hub-spoke/main.tf b/examples/02-hub-spoke/main.tf index f91c658..d42bff9 100644 --- a/examples/02-hub-spoke/main.tf +++ b/examples/02-hub-spoke/main.tf @@ -49,42 +49,21 @@ module "management" { labels = var.labels } -########################### -## CONNECTIVITY - GLOBAL ## -########################### +################## +## CONNECTIVITY ## +################## -module "connectivity_global" { - source = "../../modules/connectivity-global" +module "connectivity" { + source = "../../modules/connectivity" - owner_email = var.owner_email - naming_pattern = "${var.company_code}-pltfm-net-prod" - parent_container_id = module.governance.folder_container_ids["platform"] - organization_id = var.organization_id - labels = var.labels - dns_zones = var.dns_zones -} - -############################# -## CONNECTIVITY - REGIONAL ## -############################# - -module "connectivity_regional" { - source = "../../modules/connectivity-regional" - - project_id = module.connectivity_global.project_id + owner_email = var.owner_email + naming_pattern = "${var.company_code}-pltfm-hub-prod" + parent_container_id = module.governance.folder_container_ids["platform"] organization_id = var.organization_id - network_area_name = "${var.company_code}-pltfm-hub-prod" - network_ranges = var.network_ranges - transfer_network_range = var.transfer_network_range - min_prefix_length = var.min_prefix_length - max_prefix_length = var.max_prefix_length - default_prefix_length = var.default_prefix_length labels = var.labels - firewall_enabled = var.firewall_enabled - firewall_zone = var.firewall_zone - firewall_flavor = var.firewall_flavor - vnet_range = var.connectivity_vnet_range - firewall_ip = var.firewall_ip + dns_zones = var.dns_zones + network_area = var.network_area + firewall = var.firewall } ############ @@ -126,12 +105,13 @@ module "landing_zone" { organization_id = var.organization_id parent_container_id = each.value.corporate ? module.governance.folder_container_ids["landing_zones_corporate"] : module.governance.folder_container_ids["landing_zones_public"] naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" - dns_zone_name = "${each.value.project_code}.${each.value.env}.${var.region}.${values(module.connectivity_global.dns_zone_dns_names)[0]}" - network_area_id = each.value.corporate ? module.connectivity_regional.network_area_id : null + dns_zone_name = "${each.value.project_code}-${each.value.env}-${var.region}-${split(".", values(module.connectivity.dns_zone_dns_names)[0])[0]}.stackit.run" + network_area_id = each.value.corporate ? module.connectivity.network_area_id : null + corporate = each.value.corporate owner_email = each.value.owner_email labels = var.labels role_assignments = each.value.role_assignments network_prefix_length = each.value.network_prefix_length custom_roles = each.value.custom_roles - firewall_next_hop_ip = module.connectivity_regional.firewall_next_hop_ip + firewall_next_hop_ip = module.connectivity.firewall_next_hop_ip } diff --git a/examples/02-hub-spoke/outputs.tf b/examples/02-hub-spoke/outputs.tf index 2688ad8..2d10d67 100644 --- a/examples/02-hub-spoke/outputs.tf +++ b/examples/02-hub-spoke/outputs.tf @@ -17,19 +17,19 @@ output "management_project_id" { value = module.management.project_id } -output "connectivity_regional_network_area_id" { +output "connectivity_network_area_id" { description = "The network area ID created by the regional module." - value = module.connectivity_regional.network_area_id + value = module.connectivity.network_area_id } -output "connectivity_global_project_id" { +output "connectivity_project_id" { description = "The project ID of the connectivity project." - value = module.connectivity_global.project_id + value = module.connectivity.project_id } -output "connectivity_regional_firewall_public_ip" { +output "connectivity_firewall_public_ip" { description = "The public IP of the firewall." - value = module.connectivity_regional.firewall_public_ip + value = module.connectivity.firewall_public_ip } output "sandbox_projects" { diff --git a/examples/02-hub-spoke/terraform.tfvars b/examples/02-hub-spoke/terraform.tfvars index 0de74f7..2274c3e 100644 --- a/examples/02-hub-spoke/terraform.tfvars +++ b/examples/02-hub-spoke/terraform.tfvars @@ -1,5 +1,9 @@ +###################### +## GENERAL SETTINGS ## +###################### + # Email of the technical owner registered in STACKIT -owner_email = "platform-team@example.com" +owner_email = "matthias.hauber@prodyna.com" # Company name used for folder naming in the resource manager company_name = "Example Corp" @@ -8,7 +12,7 @@ company_name = "Example Corp" company_code = "exc" # Root organization container ID from STACKIT resource manager -organization_id = "org-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" region = "eu01" @@ -16,17 +20,22 @@ region = "eu01" labels = { managed_by = "opentofu" environment = "production" + "preview/routingtables" = "true" } # Users with full organization-level owner permissions -organization_owners = [ - "org-owner@example.com" -] +# organization_owners = [ +# "org-owner@example.com" +# ] -# Users with read-only audit access at the organization level -organization_auditors = [ - "auditor@example.com" -] +# # Users with read-only audit access at the organization level +# organization_auditors = [ +# "auditor@example.com" +# ] + +################## +## CONNECTIVITY ## +################## # DNS zones managed in the connectivity project dns_zones = { @@ -35,93 +44,79 @@ dns_zones = { } } -# IP ranges that will be sliced into per-project subnets -network_ranges = [ - { prefix = "10.1.0.0/16" }, - { prefix = "10.2.0.0/16" } -] - -# Transfer network used for routing between projects in this area -transfer_network_range = "10.255.0.0/24" - -# Controls the subnet sizes assigned to individual projects -min_prefix_length = 24 -max_prefix_length = 28 -default_prefix_length = 25 - -# Set to false to skip firewall deployment (network area and routing still created) -# firewall_enabled = false - -# Availability zone for the firewall VM -firewall_zone = "eu01-m" - -# VM flavor for the pfSense firewall -firewall_flavor = "c1.2" - -# CIDR range for the connectivity project's own VNet (firewall LAN side) -connectivity_vnet_range = "10.0.0.0/24" +# Network area configuration for the connectivity hub +network_area = { + ranges = ["10.0.0.0/16"] + transfer_network = "10.255.0.0/24" + min_prefix_length = 24 + max_prefix_length = 28 + default_prefix_length = 25 +} -# Static LAN IP of the pfSense firewall (used as default gateway) -firewall_ip = "10.0.0.220" +# Delete the variable to skip firewall deployment (network area and routing still created) +firewall = { + zone = "eu01-m" + flavor = "c1.2" + lan_ip = "10.0.0.220" + lan_prefix = "10.0.0.128/25" + wan_ip = "10.0.1.1" + wan_prefix = "10.0.1.0/25" +} ############### ## SANDBOXES ## ############### # Sandbox projects for experimentation / PoCs -sandboxes = [ - { - project_name = "Sandbox Team Alpha" - project_owner_email = "alpha-lead@example.com" - owner_emails = ["dev1@example.com", "dev2@example.com"] - }, - { - project_name = "Sandbox Data Science" - project_owner_email = "ds-lead@example.com" - } -] - -################## -## LANDING ZONE ## -################## +# sandboxes = [ +# { +# project_name = "Sandbox Team Alpha" +# project_owner_email = "alpha-lead@example.com" +# owner_emails = ["dev1@example.com", "dev2@example.com"] +# }, +# ] + +################### +## LANDING ZONES ## +################### # Landing zones keyed by a unique identifier # Set corporate = true for network area connectivity, false for public internet landing_zones = { - "app-backend" = { - project_name = "Backend Services" - project_code = "be" - owner_email = "backend-team@example.com" - env = "prod" - corporate = true - - # Subnet size assigned from the network area (/25 = 128 addresses) - network_prefix_length = 25 - - role_assignments = [ - { - role = "project.owner" - subject = "backend-lead@example.com" - }, - { - role = "project.member" - subject = "backend-dev@example.com" - } - ] - - custom_roles = [ - { - name = "deployer" - description = "Can deploy workloads" - permissions = ["project.resources.read", "project.resources.write"] - } - ] - } + # "app-backend" = { + # project_name = "Backend Services" + # project_code = "be" + # owner_email = "backend-team@example.com" + # env = "prod" + # corporate = true + + # # Subnet size assigned from the network area (/25 = 128 addresses) + # network_prefix_length = 25 + + # role_assignments = [ + # { + # role = "project.owner" + # subject = "backend-lead@example.com" + # }, + # { + # role = "project.member" + # subject = "backend-dev@example.com" + # } + # ] + + # custom_roles = [ + # { + # name = "deployer" + # description = "Can deploy workloads" + # permissions = ["project.resources.read", "project.resources.write"] + # } + # ] + # } "data-platform" = { project_name = "Data Platform" project_code = "data" - owner_email = "data-team@example.com" + owner_email = "matthias.hauber@prodyna.com" env = "prod" corporate = true @@ -132,15 +127,15 @@ landing_zones = { "external-api" = { project_name = "External API Gateway" project_code = "api" - owner_email = "api-team@example.com" + owner_email = "matthias.hauber@prodyna.com" env = "prod" corporate = false - role_assignments = [ - { - role = "project.owner" - subject = "api-lead@example.com" - } - ] + # role_assignments = [ + # { + # role = "project.owner" + # subject = "api-lead@example.com" + # } + # ] } } diff --git a/examples/02-hub-spoke/variables.tf b/examples/02-hub-spoke/variables.tf index 431910a..7e1c60d 100644 --- a/examples/02-hub-spoke/variables.tf +++ b/examples/02-hub-spoke/variables.tf @@ -70,62 +70,28 @@ variable "dns_zones" { ## CONNECTIVITY - REGIONAL ## ############################# -variable "network_ranges" { - type = list(object({ prefix = string })) - description = "IP ranges that will be sliced into per-project subnets." -} - -variable "transfer_network_range" { - type = string - description = "Transfer network CIDR used for routing between projects in this area." -} - -variable "max_prefix_length" { - type = number - description = "Maximum prefix length for subnets assigned to projects." - default = 28 -} - -variable "min_prefix_length" { - type = number - description = "Minimum prefix length for subnets assigned to projects." - default = 24 -} - -variable "default_prefix_length" { - type = number - description = "Default prefix length for subnets assigned to projects." - default = 28 -} - -variable "firewall_enabled" { - type = bool - description = "Whether to deploy the pfSense firewall. Set to false for connectivity without a firewall (network area and routing are still created)." - default = true -} - -variable "firewall_zone" { - type = string - description = "STACKIT Availability Zone for the firewall VM." - default = "eu01-m" -} - -variable "firewall_flavor" { - type = string - description = "Firewall VM flavor." - default = "c1.2" -} - -variable "connectivity_vnet_range" { - type = string - description = "CIDR range for the connectivity project VNet." - default = "10.0.0.0/24" -} - -variable "firewall_ip" { - type = string - description = "Static IP address for the firewall LAN interface." - default = "10.0.0.220" +variable "network_area" { + type = object({ + ranges = list(string) + transfer_network = string + min_prefix_length = optional(number, 24) + max_prefix_length = optional(number, 28) + default_prefix_length = optional(number, 28) + }) + description = "Network area configuration including IP ranges, transfer network, and prefix length settings." +} + +variable "firewall" { + type = object({ + zone = string + flavor = string + lan_ip = string + lan_prefix = string + wan_ip = string + wan_prefix = string + }) + description = "pfSense firewall configuration. Set to null to skip firewall deployment." + default = null } ############### diff --git a/modules/connectivity-global/1-project.tf b/modules/connectivity-global/1-project.tf deleted file mode 100644 index 8e6a60f..0000000 --- a/modules/connectivity-global/1-project.tf +++ /dev/null @@ -1,18 +0,0 @@ -############# -## PROJECT ## -############# - -resource "stackit_resourcemanager_project" "this" { - parent_container_id = var.parent_container_id - name = var.project_name != null ? var.project_name : var.naming_pattern - owner_email = var.owner_email - labels = length(var.labels) > 0 ? var.labels : null # provider bug: empty map becomes null after apply -} - -resource "stackit_authorization_project_role_assignment" "this" { - for_each = { for assignment in var.role_assignments : "${assignment.role}-${assignment.subject}" => assignment } - - resource_id = stackit_resourcemanager_project.this.project_id - role = each.value.role - subject = each.value.subject -} \ No newline at end of file diff --git a/modules/connectivity-global/2-dns-zones.tf b/modules/connectivity-global/2-dns-zones.tf deleted file mode 100644 index 48fafa5..0000000 --- a/modules/connectivity-global/2-dns-zones.tf +++ /dev/null @@ -1,12 +0,0 @@ -resource "stackit_dns_zone" "this" { - for_each = var.dns_zones - - project_id = stackit_resourcemanager_project.this.project_id - name = each.value.name != null ? each.value.name : each.value.dns_name - dns_name = each.value.dns_name - contact_email = each.value.contact_email - type = each.value.type - acl = each.value.acl - description = each.value.description - default_ttl = each.value.default_ttl -} \ No newline at end of file diff --git a/modules/connectivity-global/README.md b/modules/connectivity-global/README.md deleted file mode 100644 index bae0828..0000000 --- a/modules/connectivity-global/README.md +++ /dev/null @@ -1,42 +0,0 @@ - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.88.0 | - -### Providers - -| Name | Version | -|------|---------| -| [stackit](#provider\_stackit) | 0.88.0 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [stackit_network_area.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_area) | resource | -| [stackit_network_area_region.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_area_region) | resource | -| [stackit_network_area_route.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_area_route) | resource | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [network\_areas](#input\_network\_areas) | List of network areas to create, each with its own name, ranges, and configuration. |
list(object({
name = string
network_ranges = list(object({ prefix = string }))
transfer_network_range = string
max_prefix_length = optional(number, 28)
min_prefix_length = optional(number, 24)
default_prefix_length = optional(number, 28)
default_nameservers = optional(list(string), null)
}))
| n/a | yes | -| [organization\_id](#input\_organization\_id) | Container ID of the root folder or organization under which the company folder will be created. | `string` | n/a | yes | -| [labels](#input\_labels) | Additional labels to apply to all folders. | `map(string)` | `{}` | no | -| [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if network is enabled. | `string` | `null` | no | -| [network\_area\_routes](#input\_network\_area\_routes) | List of static routes to create within network areas. Each route references a network area by name. |
list(object({
name = string
network_area_name = string
destination = object({
type = string
value = string
})
next_hop = object({
type = string
value = optional(string)
})
}))
| `[]` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [network\_area\_ids](#output\_network\_area\_ids) | Map of network area names to their IDs. | - \ No newline at end of file diff --git a/modules/connectivity-global/terraform.tf b/modules/connectivity-global/terraform.tf deleted file mode 100644 index 9b65a85..0000000 --- a/modules/connectivity-global/terraform.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.10" - - required_providers { - stackit = { - source = "stackitcloud/stackit" - version = ">=0.93.0" - } - } -} diff --git a/modules/connectivity-regional/1-network-area.tf b/modules/connectivity-regional/1-network-area.tf deleted file mode 100644 index 3ce9f15..0000000 --- a/modules/connectivity-regional/1-network-area.tf +++ /dev/null @@ -1,23 +0,0 @@ -################## -## NETWORK AREA ## -################## - -resource "stackit_network_area" "this" { - organization_id = var.organization_id - name = var.network_area_name - labels = merge(var.labels, { "preview/routingtables" = "true" }) -} - -resource "stackit_network_area_region" "this" { - organization_id = var.organization_id - network_area_id = stackit_network_area.this.network_area_id - - ipv4 = { - network_ranges = var.network_ranges - transfer_network = var.transfer_network_range - max_prefix_length = var.max_prefix_length - min_prefix_length = var.min_prefix_length - default_prefix_length = var.default_prefix_length - default_nameservers = var.default_nameservers - } -} diff --git a/modules/connectivity-regional/3-internal-network.tf b/modules/connectivity-regional/3-internal-network.tf deleted file mode 100644 index 88f6cea..0000000 --- a/modules/connectivity-regional/3-internal-network.tf +++ /dev/null @@ -1,20 +0,0 @@ -############# -## NETWORK ## -############# - -resource "stackit_network" "lan" { - count = var.firewall_enabled ? 1 : 0 - - project_id = var.project_id - name = "lan_network" - routed = true -} - -resource "stackit_network_interface" "lan" { - count = var.firewall_enabled ? 1 : 0 - - name = "vtnet1_lan" - project_id = var.project_id - network_id = stackit_network.lan[0].network_id - security = false -} \ No newline at end of file diff --git a/modules/connectivity-regional/outputs.tf b/modules/connectivity-regional/outputs.tf deleted file mode 100644 index 507ccd1..0000000 --- a/modules/connectivity-regional/outputs.tf +++ /dev/null @@ -1,14 +0,0 @@ -output "firewall_public_ip" { - description = "The public IP address of the pfSense firewall WAN interface." - value = var.firewall_enabled ? stackit_public_ip.wan-ip[0].ip : null -} - -output "firewall_next_hop_ip" { - description = "The IP address to be used as next hop for the default route in the landing zones (pfSense WAN IP)." - value = var.firewall_enabled ? stackit_network_interface.lan[0].ipv4 : null -} - -output "network_area_id" { - description = "The ID of the created network area." - value = stackit_network_area.this.network_area_id -} diff --git a/modules/connectivity-regional/pfsense.qcow2 b/modules/connectivity-regional/pfsense.qcow2 deleted file mode 100644 index 311c8dd..0000000 --- a/modules/connectivity-regional/pfsense.qcow2 +++ /dev/null @@ -1 +0,0 @@ -PLACEHOLDER \ No newline at end of file diff --git a/modules/connectivity-regional/variables.tf b/modules/connectivity-regional/variables.tf deleted file mode 100644 index a4fa64c..0000000 --- a/modules/connectivity-regional/variables.tf +++ /dev/null @@ -1,89 +0,0 @@ -variable "firewall_enabled" { - type = bool - description = "Whether to deploy the pfSense firewall and associated WAN/LAN networks. Set to false for connectivity without a firewall." - default = true -} - -variable "firewall_flavor" { - type = string - description = "Firewall VM Flavor" - default = "c1.2" - - validation { - condition = can(regex("^[a-z][0-9]+\\.[0-9]+$", var.firewall_flavor)) - error_message = "firewall_flavor must match STACKIT machine type format (e.g. c1.2). Validate available flavors with: stackit server machine-type list" - } -} - -variable "firewall_ip" { - type = string - description = "IP address of the firewall" - default = "10.0.0.220" -} - -variable "firewall_zone" { - type = string - description = "STACKIT Availability Zone" - default = "eu01-m" -} - -variable "labels" { - type = map(string) - description = "Additional labels to apply to all resources." - default = {} -} - -variable "network_area_name" { - type = string - description = "Name of the network area to create for this region." -} - -variable "network_ranges" { - type = list(object({ prefix = string })) - description = "IP ranges that will be sliced into per-project subnets." -} - -variable "transfer_network_range" { - type = string - description = "Transfer network CIDR used for routing between projects in this area." -} - -variable "max_prefix_length" { - type = number - description = "Maximum prefix length for subnets assigned to projects." - default = 28 -} - -variable "min_prefix_length" { - type = number - description = "Minimum prefix length for subnets assigned to projects." - default = 24 -} - -variable "default_prefix_length" { - type = number - description = "Default prefix length for subnets assigned to projects." - default = 28 -} - -variable "default_nameservers" { - type = list(string) - description = "Default nameservers for the network area." - default = ["1.0.0.1", "1.1.1.1"] -} - -variable "organization_id" { - type = string - description = "Organization ID, required for network area and route configuration." -} - -variable "project_id" { - type = string - description = "Project ID of the connectivity project (created by connectivity-global)." -} - -variable "vnet_range" { - type = string - description = "CIDR range for the project VNet. Required if network is enabled." - default = "10.0.0.0/24" -} diff --git a/modules/connectivity/1-network-area.tf b/modules/connectivity/1-network-area.tf new file mode 100644 index 0000000..ab50e8c --- /dev/null +++ b/modules/connectivity/1-network-area.tf @@ -0,0 +1,23 @@ +################## +## NETWORK AREA ## +################## + +resource "stackit_network_area" "this" { + organization_id = var.organization_id + name = var.network_area_name != null ? var.network_area_name : var.naming_pattern + labels = merge(var.labels, { "preview/routingtables" = "true" }) +} + +resource "stackit_network_area_region" "this" { + organization_id = var.organization_id + network_area_id = stackit_network_area.this.network_area_id + + ipv4 = { + network_ranges = [for r in var.network_area.ranges : { prefix = r }] + transfer_network = var.network_area.transfer_network + max_prefix_length = var.network_area.max_prefix_length + min_prefix_length = var.network_area.min_prefix_length + default_prefix_length = var.network_area.default_prefix_length + default_nameservers = var.network_area.default_nameservers + } +} diff --git a/modules/connectivity/2-project.tf b/modules/connectivity/2-project.tf new file mode 100644 index 0000000..8fca9d7 --- /dev/null +++ b/modules/connectivity/2-project.tf @@ -0,0 +1,43 @@ +############# +## PROJECT ## +############# + +locals { + project_labels = merge( + { "networkArea" = stackit_network_area.this.id }, + var.labels + ) + labels = length(local.project_labels) > 0 ? local.project_labels : null # provider bug: empty map becomes null after apply +} + +resource "stackit_resourcemanager_project" "this" { + parent_container_id = var.parent_container_id + name = var.project_name != null ? var.project_name : var.naming_pattern + owner_email = var.owner_email + labels = local.labels +} + +resource "stackit_authorization_project_role_assignment" "this" { + for_each = { for assignment in var.role_assignments : "${assignment.role}-${assignment.subject}" => assignment } + + resource_id = stackit_resourcemanager_project.this.project_id + role = each.value.role + subject = each.value.subject +} + +############### +## DNS ZONES ## +############### + +resource "stackit_dns_zone" "this" { + for_each = var.dns_zones + + project_id = stackit_resourcemanager_project.this.project_id + name = each.value.name != null ? each.value.name : each.value.dns_name + dns_name = each.value.dns_name + contact_email = each.value.contact_email + type = each.value.type + acl = each.value.acl + description = each.value.description + default_ttl = each.value.default_ttl +} diff --git a/modules/connectivity-regional/2-external-network.tf b/modules/connectivity/3-external-network.tf similarity index 64% rename from modules/connectivity-regional/2-external-network.tf rename to modules/connectivity/3-external-network.tf index 290afad..615fc0c 100644 --- a/modules/connectivity-regional/2-external-network.tf +++ b/modules/connectivity/3-external-network.tf @@ -2,11 +2,19 @@ ## ROUTING ## ############# +resource "time_sleep" "wait_for_network_area" { + create_duration = "20s" + + depends_on = [stackit_network_area.this] +} + resource "stackit_routing_table" "wan" { organization_id = var.organization_id network_area_id = stackit_network_area.this.network_area_id name = "wan" system_routes = true + + depends_on = [time_sleep.wait_for_network_area] } resource "stackit_routing_table_route" "wan" { @@ -29,26 +37,28 @@ resource "stackit_routing_table_route" "wan" { ############# resource "stackit_network" "wan" { - count = var.firewall_enabled ? 1 : 0 + count = var.firewall != null ? 1 : 0 - project_id = var.project_id + project_id = stackit_resourcemanager_project.this.project_id name = "wan_network" + ipv4_prefix = var.firewall.wan_prefix routing_table_id = stackit_routing_table.wan.routing_table_id routed = true } resource "stackit_network_interface" "wan" { - count = var.firewall_enabled ? 1 : 0 + count = var.firewall != null ? 1 : 0 name = "vtnet0_wan" - project_id = var.project_id + project_id = stackit_resourcemanager_project.this.project_id network_id = stackit_network.wan[0].network_id + ipv4 = var.firewall.wan_ip security = false } resource "stackit_public_ip" "wan-ip" { - count = var.firewall_enabled ? 1 : 0 + count = var.firewall != null ? 1 : 0 - project_id = var.project_id + project_id = stackit_resourcemanager_project.this.project_id network_interface_id = stackit_network_interface.wan[0].network_interface_id } \ No newline at end of file diff --git a/modules/connectivity/4-internal-network.tf b/modules/connectivity/4-internal-network.tf new file mode 100644 index 0000000..723d5b9 --- /dev/null +++ b/modules/connectivity/4-internal-network.tf @@ -0,0 +1,22 @@ +############# +## NETWORK ## +############# + +resource "stackit_network" "lan" { + count = var.firewall != null ? 1 : 0 + + project_id = stackit_resourcemanager_project.this.project_id + name = "lan_network" + ipv4_prefix = var.firewall.lan_prefix + routed = true +} + +resource "stackit_network_interface" "lan" { + count = var.firewall != null ? 1 : 0 + + name = "vtnet1_lan" + project_id = stackit_resourcemanager_project.this.project_id + network_id = stackit_network.lan[0].network_id + ipv4 = var.firewall.lan_ip + security = false +} \ No newline at end of file diff --git a/modules/connectivity-regional/4-firewall.tf b/modules/connectivity/5-firewall.tf similarity index 76% rename from modules/connectivity-regional/4-firewall.tf rename to modules/connectivity/5-firewall.tf index c2efea4..ff0c44a 100644 --- a/modules/connectivity-regional/4-firewall.tf +++ b/modules/connectivity/5-firewall.tf @@ -13,9 +13,9 @@ # } resource "stackit_image" "pfsense_image" { - count = var.firewall_enabled ? 1 : 0 + count = var.firewall != null ? 1 : 0 - project_id = var.project_id + project_id = stackit_resourcemanager_project.this.project_id name = "pfsense-2.7.2-amd64-image" local_file_path = "./pfsense.qcow2" disk_format = "qcow2" @@ -33,11 +33,11 @@ resource "stackit_image" "pfsense_image" { ############ resource "stackit_volume" "pfsense_vol" { - count = var.firewall_enabled ? 1 : 0 + count = var.firewall != null ? 1 : 0 - project_id = var.project_id + project_id = stackit_resourcemanager_project.this.project_id name = "pfsense-2.7.2-root" - availability_zone = var.firewall_zone + availability_zone = var.firewall.zone size = 16 performance_class = "storage_premium_perf4" source = { @@ -52,16 +52,16 @@ resource "stackit_volume" "pfsense_vol" { # after rollout: https://docs.stackit.cloud/products/quick-deployments/pfsense-firewall/tutorials/configure-pfsense/ resource "stackit_server" "pfsense_Server" { - count = var.firewall_enabled ? 1 : 0 + count = var.firewall != null ? 1 : 0 - project_id = var.project_id + project_id = stackit_resourcemanager_project.this.project_id name = "pfSense" boot_volume = { source_type = "volume" source_id = stackit_volume.pfsense_vol[0].volume_id } - availability_zone = var.firewall_zone - machine_type = var.firewall_flavor + availability_zone = var.firewall.zone + machine_type = var.firewall.flavor network_interfaces = [ stackit_network_interface.wan[0].network_interface_id, # vtnet0 = WAN diff --git a/modules/connectivity-regional/README.md b/modules/connectivity/README.md similarity index 99% rename from modules/connectivity-regional/README.md rename to modules/connectivity/README.md index 8e062df..df995ba 100644 --- a/modules/connectivity-regional/README.md +++ b/modules/connectivity/README.md @@ -82,7 +82,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-net-prod". | `string` | n/a | yes | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | | [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if network is enabled. | `string` | n/a | yes | | [organization\_id](#input\_organization\_id) | Organization ID, required for network area route configuration. | `string` | n/a | yes | | [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | diff --git a/modules/connectivity-global/outputs.tf b/modules/connectivity/outputs.tf similarity index 57% rename from modules/connectivity-global/outputs.tf rename to modules/connectivity/outputs.tf index 8f9d063..0817e90 100644 --- a/modules/connectivity-global/outputs.tf +++ b/modules/connectivity/outputs.tf @@ -1,3 +1,28 @@ +output "dns_zone_dns_names" { + description = "Map of DNS zone keys to their DNS names" + value = { for k, z in stackit_dns_zone.this : k => z.dns_name } +} + +output "dns_zone_ids" { + description = "Map of DNS zone keys to their zone IDs" + value = { for k, z in stackit_dns_zone.this : k => z.zone_id } +} + +output "firewall_next_hop_ip" { + description = "The IP address to be used as next hop for the default route in the landing zones (pfSense WAN IP)." + value = var.firewall != null ? stackit_network_interface.lan[0].ipv4 : null +} + +output "firewall_public_ip" { + description = "The public IP address of the pfSense firewall WAN interface." + value = var.firewall != null ? stackit_public_ip.wan-ip[0].ip : null +} + +output "network_area_id" { + description = "The ID of the created network area." + value = stackit_network_area.this.network_area_id +} + output "project_container_id" { description = "The container ID of the created STACKIT project." value = stackit_resourcemanager_project.this.container_id @@ -12,13 +37,3 @@ output "project_name" { description = "The name of the created STACKIT project." value = stackit_resourcemanager_project.this.name } - -output "dns_zone_ids" { - description = "Map of DNS zone keys to their zone IDs" - value = { for k, z in stackit_dns_zone.this : k => z.zone_id } -} - -output "dns_zone_dns_names" { - description = "Map of DNS zone keys to their DNS names" - value = { for k, z in stackit_dns_zone.this : k => z.dns_name } -} \ No newline at end of file diff --git a/modules/connectivity-regional/terraform.tf b/modules/connectivity/terraform.tf similarity index 66% rename from modules/connectivity-regional/terraform.tf rename to modules/connectivity/terraform.tf index 851721e..7932297 100644 --- a/modules/connectivity-regional/terraform.tf +++ b/modules/connectivity/terraform.tf @@ -6,5 +6,9 @@ terraform { source = "stackitcloud/stackit" version = ">=0.93.0" } + time = { + source = "hashicorp/time" + version = ">= 0.13.0" + } } } \ No newline at end of file diff --git a/modules/connectivity-global/variables.tf b/modules/connectivity/variables.tf similarity index 50% rename from modules/connectivity-global/variables.tf rename to modules/connectivity/variables.tf index b964f98..b35ed6c 100644 --- a/modules/connectivity-global/variables.tf +++ b/modules/connectivity/variables.tf @@ -1,3 +1,35 @@ +variable "dns_zones" { + type = map(object({ + dns_name = string + name = optional(string, null) + contact_email = optional(string, null) + type = optional(string, "primary") + acl = optional(string, null) + description = optional(string, null) + default_ttl = optional(number, 3600) + })) + description = "Map of DNS zone keys to DNS zone configuration. Name defaults to dns_name if not set." + default = {} +} + +variable "firewall" { + type = object({ + zone = string + flavor = string + lan_ip = string + lan_prefix = string + wan_ip = string + wan_prefix = string + }) + description = "pfSense firewall configuration. Set to null to skip firewall deployment (network area and routing are still created). lan_prefix and wan_prefix must be CIDRs within the network area range that contain the respective IPs." + default = null + + validation { + condition = var.firewall == null || can(regex("^[a-z][0-9]+\\.[0-9]+$", var.firewall.flavor)) + error_message = "firewall.flavor must match STACKIT machine type format (e.g. c1.2). Validate available flavors with: stackit server machine-type list" + } +} + variable "labels" { type = map(string) description = "Additional labels to apply to all resources." @@ -6,12 +38,30 @@ variable "labels" { variable "naming_pattern" { type = string - description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-net-prod\"." + description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-hub-prod\"." +} + +variable "network_area" { + type = object({ + ranges = list(string) + transfer_network = string + min_prefix_length = optional(number, 24) + max_prefix_length = optional(number, 28) + default_prefix_length = optional(number, 28) + default_nameservers = optional(list(string), ["1.0.0.1", "1.1.1.1"]) + }) + description = "Network area configuration including IP ranges, transfer network, and prefix length settings." +} + +variable "network_area_name" { + type = string + description = "Name of the network area to create for this region." + default = null } variable "organization_id" { type = string - description = "Container ID of the root folder or organization under which the company folder will be created." + description = "Organization ID, required for network area and route configuration." } variable "owner_email" { @@ -37,18 +87,4 @@ variable "role_assignments" { })) description = "List of role assignments for the project. Subject can be a user email or service account email." default = [] -} - -variable "dns_zones" { - type = map(object({ - dns_name = string - name = optional(string, null) - contact_email = optional(string, null) - type = optional(string, "primary") - acl = optional(string, null) - description = optional(string, null) - default_ttl = optional(number, 3600) - })) - description = "Map of DNS zone keys to DNS zone configuration. Name defaults to dns_name if not set." - default = {} } \ No newline at end of file diff --git a/modules/devops/README.md b/modules/devops/README.md index c2deca5..3e0c95e 100644 --- a/modules/devops/README.md +++ b/modules/devops/README.md @@ -29,7 +29,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [company\_name](#input\_company\_name) | Name of the company folder to create. | `string` | n/a | yes | -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-net-prod". | `string` | n/a | yes | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | | [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | | [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | | [allowed\_network\_ranges](#input\_allowed\_network\_ranges) | List of allowed network ranges for Git instance ACL. | `list(string)` |
[
"0.0.0.0/0"
]
| no | diff --git a/modules/devops/variables.tf b/modules/devops/variables.tf index 631f67f..87deb26 100644 --- a/modules/devops/variables.tf +++ b/modules/devops/variables.tf @@ -50,7 +50,7 @@ variable "project_name" { variable "naming_pattern" { type = string - description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-net-prod\"." + description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-hub-prod\"." } variable "role_assignments" { diff --git a/modules/landing-zone/3-network.tf b/modules/landing-zone/3-network.tf index d4d05da..f95d564 100644 --- a/modules/landing-zone/3-network.tf +++ b/modules/landing-zone/3-network.tf @@ -2,7 +2,7 @@ ## ROUTING ## ############# resource "stackit_routing_table" "this" { - count = var.network_area_id != null ? 1 : 0 + count = var.corporate ? 1 : 0 organization_id = var.organization_id network_area_id = var.network_area_id name = var.naming_pattern @@ -12,7 +12,7 @@ resource "stackit_routing_table" "this" { } resource "stackit_routing_table_route" "this" { - count = var.network_area_id != null ? 1 : 0 + count = var.corporate ? 1 : 0 routing_table_id = stackit_routing_table.this[0].routing_table_id organization_id = var.organization_id @@ -35,7 +35,7 @@ resource "stackit_routing_table_route" "this" { ## NETWORK ## ############# resource "stackit_network" "this" { - count = var.network_area_id != null ? 1 : 0 + count = var.corporate ? 1 : 0 project_id = stackit_resourcemanager_project.this.project_id name = "${var.naming_pattern}-routed" diff --git a/modules/landing-zone/README.md b/modules/landing-zone/README.md index 21d0012..3f3b3f8 100644 --- a/modules/landing-zone/README.md +++ b/modules/landing-zone/README.md @@ -42,7 +42,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [custom\_roles](#input\_custom\_roles) | List of custom roles to create for the project. |
list(object({
name = string
description = string
permissions = list(string)
}))
| n/a | yes | -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-net-prod". | `string` | n/a | yes | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | | [owner\_email](#input\_owner\_email) | Email address of the project owner. Required for project creation. | `string` | n/a | yes | | [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | | [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no | diff --git a/modules/landing-zone/variables.tf b/modules/landing-zone/variables.tf index 28300a8..ce78c23 100644 --- a/modules/landing-zone/variables.tf +++ b/modules/landing-zone/variables.tf @@ -14,7 +14,7 @@ variable "custom_roles" { variable "naming_pattern" { type = string - description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-net-prod\"." + description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-hub-prod\"." } variable "labels" { @@ -25,10 +25,16 @@ variable "labels" { variable "network_area_id" { type = string - description = "Network Area ID to deploy resources into. Required if network is enabled." + description = "Network Area ID to deploy resources into. Required if corporate is true." default = null } +variable "corporate" { + type = bool + description = "Whether this landing zone uses corporate networking (network area + routing). Set to false for public internet." + default = false +} + variable "network_prefix_length" { type = number description = "CIDR block prefix length for the project's network range." diff --git a/modules/management/README.md b/modules/management/README.md index c2094cd..31ffca2 100644 --- a/modules/management/README.md +++ b/modules/management/README.md @@ -39,7 +39,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-net-prod". | `string` | n/a | yes | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | | [organization\_id](#input\_organization\_id) | Container ID of the root folder or organization under which the company folder will be created. | `string` | n/a | yes | | [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | | [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | diff --git a/modules/management/variables.tf b/modules/management/variables.tf index 2fd3874..99e2316 100644 --- a/modules/management/variables.tf +++ b/modules/management/variables.tf @@ -6,7 +6,7 @@ variable "project_name" { variable "naming_pattern" { type = string - description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-net-prod\"." + description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-hub-prod\"." } variable "labels" { diff --git a/modules/sandboxes/README.md b/modules/sandboxes/README.md index aca42a5..355c527 100644 --- a/modules/sandboxes/README.md +++ b/modules/sandboxes/README.md @@ -27,7 +27,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-net-prod". | `string` | n/a | yes | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | | [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | | [project\_name](#input\_project\_name) | Name of the STACKIT project to create. | `string` | `null` | no | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | From 7530e2b6d1f22f3eb7c2d2e0b9181e69b7e40c73 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 28 Apr 2026 11:41:09 +0200 Subject: [PATCH 04/30] feat: refactor firewall configuration and update related variables and outputs Co-authored-by: Copilot --- examples/01-standalone/main.tf | 2 +- examples/02-hub-spoke/main.tf | 40 +++++++++++----------- examples/02-hub-spoke/outputs.tf | 2 ++ examples/02-hub-spoke/terraform.tfvars | 15 ++++---- examples/02-hub-spoke/variables.tf | 10 +++--- modules/connectivity/2-project.tf | 2 +- modules/connectivity/3-external-network.tf | 4 +-- modules/connectivity/4-internal-network.tf | 4 +-- modules/connectivity/variables.tf | 10 +++--- modules/governance/1-rm-folders.tf | 6 +++- modules/landing-zone/3-network.tf | 17 ++++----- modules/landing-zone/outputs.tf | 10 ++++++ 12 files changed, 68 insertions(+), 54 deletions(-) diff --git a/examples/01-standalone/main.tf b/examples/01-standalone/main.tf index d6c7885..bb39d20 100644 --- a/examples/01-standalone/main.tf +++ b/examples/01-standalone/main.tf @@ -67,7 +67,7 @@ module "sandboxes" { source = "../../modules/sandboxes" count = length(var.sandboxes) > 0 ? 1 : 0 - naming_prefix = "${var.company_code}-sbx" + naming_prefix = "${var.company_code}-sbx" parent_container_id = module.governance.folder_container_ids["sandboxes"] sandboxes = var.sandboxes } diff --git a/examples/02-hub-spoke/main.tf b/examples/02-hub-spoke/main.tf index d42bff9..8f4d3ef 100644 --- a/examples/02-hub-spoke/main.tf +++ b/examples/02-hub-spoke/main.tf @@ -56,14 +56,14 @@ module "management" { module "connectivity" { source = "../../modules/connectivity" - owner_email = var.owner_email - naming_pattern = "${var.company_code}-pltfm-hub-prod" - parent_container_id = module.governance.folder_container_ids["platform"] - organization_id = var.organization_id - labels = var.labels - dns_zones = var.dns_zones - network_area = var.network_area - firewall = var.firewall + owner_email = var.owner_email + naming_pattern = "${var.company_code}-pltfm-hub-prod" + parent_container_id = module.governance.folder_container_ids["platform"] + organization_id = var.organization_id + labels = var.labels + dns_zones = var.dns_zones + network_area = var.network_area + firewall = var.firewall } ############ @@ -102,16 +102,16 @@ module "landing_zone" { source = "../../modules/landing-zone" for_each = var.landing_zones - organization_id = var.organization_id - parent_container_id = each.value.corporate ? module.governance.folder_container_ids["landing_zones_corporate"] : module.governance.folder_container_ids["landing_zones_public"] - naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" - dns_zone_name = "${each.value.project_code}-${each.value.env}-${var.region}-${split(".", values(module.connectivity.dns_zone_dns_names)[0])[0]}.stackit.run" - network_area_id = each.value.corporate ? module.connectivity.network_area_id : null - corporate = each.value.corporate - owner_email = each.value.owner_email - labels = var.labels - role_assignments = each.value.role_assignments - network_prefix_length = each.value.network_prefix_length - custom_roles = each.value.custom_roles - firewall_next_hop_ip = module.connectivity.firewall_next_hop_ip + organization_id = var.organization_id + parent_container_id = each.value.corporate ? module.governance.folder_container_ids["landing_zones_corporate"] : module.governance.folder_container_ids["landing_zones_public"] + naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" + dns_zone_name = "${each.value.project_code}-${each.value.env}-${var.region}-${split(".", values(module.connectivity.dns_zone_dns_names)[0])[0]}.stackit.run" + network_area_id = each.value.corporate ? module.connectivity.network_area_id : null + corporate = each.value.corporate + owner_email = each.value.owner_email + labels = var.labels + role_assignments = each.value.role_assignments + network_prefix_length = each.value.network_prefix_length + custom_roles = each.value.custom_roles + firewall_next_hop_ip = var.firewall != null ? module.connectivity.firewall_next_hop_ip : null # if firewall is enabled, pass the next hop IP to the landing zones for route configuration } diff --git a/examples/02-hub-spoke/outputs.tf b/examples/02-hub-spoke/outputs.tf index 2d10d67..09f30ee 100644 --- a/examples/02-hub-spoke/outputs.tf +++ b/examples/02-hub-spoke/outputs.tf @@ -44,6 +44,8 @@ output "landing_zone_projects" { project_id = v.project_id project_name = v.project_name dns_zone_name = v.dns_zone_dns_name + landing_zone_type = v.landing_zone_type + connected_network_area_id = v.connected_network_area_id == null ? "" : v.connected_network_area_id } } } diff --git a/examples/02-hub-spoke/terraform.tfvars b/examples/02-hub-spoke/terraform.tfvars index 2274c3e..9eee37d 100644 --- a/examples/02-hub-spoke/terraform.tfvars +++ b/examples/02-hub-spoke/terraform.tfvars @@ -3,7 +3,7 @@ ###################### # Email of the technical owner registered in STACKIT -owner_email = "matthias.hauber@prodyna.com" +owner_email = "example@company.com" # Company name used for folder naming in the resource manager company_name = "Example Corp" @@ -16,11 +16,10 @@ organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" region = "eu01" -# Labels applied to all resources for cost tracking / filtering +# Labels applied to all resources, max. 64 characters labels = { managed_by = "opentofu" environment = "production" - "preview/routingtables" = "true" } # Users with full organization-level owner permissions @@ -39,8 +38,8 @@ labels = { # DNS zones managed in the connectivity project dns_zones = { - "prodyna" = { - dns_name = "prodyna.stackit.run" + "example-corp" = { + dns_name = "example-corp.stackit.run" } } @@ -57,10 +56,8 @@ network_area = { firewall = { zone = "eu01-m" flavor = "c1.2" - lan_ip = "10.0.0.220" - lan_prefix = "10.0.0.128/25" - wan_ip = "10.0.1.1" - wan_prefix = "10.0.1.0/25" + lan_network_range = "10.0.0.0/28" + wan_network_range = "10.0.0.16/28" } ############### diff --git a/examples/02-hub-spoke/variables.tf b/examples/02-hub-spoke/variables.tf index 7e1c60d..a2e7f20 100644 --- a/examples/02-hub-spoke/variables.tf +++ b/examples/02-hub-spoke/variables.tf @@ -85,12 +85,12 @@ variable "firewall" { type = object({ zone = string flavor = string - lan_ip = string - lan_prefix = string - wan_ip = string - wan_prefix = string + lan_network_range = string + wan_network_range = string + lan_ip = optional(string, null) + wan_ip = optional(string, null) }) - description = "pfSense firewall configuration. Set to null to skip firewall deployment." + description = "pfSense firewall configuration. Set to null to skip firewall deployment. lan_ip/wan_ip default to the 5th address of the respective prefix." default = null } diff --git a/modules/connectivity/2-project.tf b/modules/connectivity/2-project.tf index 8fca9d7..577ad4e 100644 --- a/modules/connectivity/2-project.tf +++ b/modules/connectivity/2-project.tf @@ -4,7 +4,7 @@ locals { project_labels = merge( - { "networkArea" = stackit_network_area.this.id }, + { "networkArea" = stackit_network_area.this.network_area_id }, var.labels ) labels = length(local.project_labels) > 0 ? local.project_labels : null # provider bug: empty map becomes null after apply diff --git a/modules/connectivity/3-external-network.tf b/modules/connectivity/3-external-network.tf index 615fc0c..d0bd8ef 100644 --- a/modules/connectivity/3-external-network.tf +++ b/modules/connectivity/3-external-network.tf @@ -41,7 +41,7 @@ resource "stackit_network" "wan" { project_id = stackit_resourcemanager_project.this.project_id name = "wan_network" - ipv4_prefix = var.firewall.wan_prefix + ipv4_prefix = var.firewall.wan_network_range routing_table_id = stackit_routing_table.wan.routing_table_id routed = true } @@ -52,7 +52,7 @@ resource "stackit_network_interface" "wan" { name = "vtnet0_wan" project_id = stackit_resourcemanager_project.this.project_id network_id = stackit_network.wan[0].network_id - ipv4 = var.firewall.wan_ip + ipv4 = coalesce(var.firewall.wan_ip, cidrhost(var.firewall.wan_network_range, 4)) security = false } diff --git a/modules/connectivity/4-internal-network.tf b/modules/connectivity/4-internal-network.tf index 723d5b9..18718a7 100644 --- a/modules/connectivity/4-internal-network.tf +++ b/modules/connectivity/4-internal-network.tf @@ -7,7 +7,7 @@ resource "stackit_network" "lan" { project_id = stackit_resourcemanager_project.this.project_id name = "lan_network" - ipv4_prefix = var.firewall.lan_prefix + ipv4_prefix = var.firewall.lan_network_range routed = true } @@ -17,6 +17,6 @@ resource "stackit_network_interface" "lan" { name = "vtnet1_lan" project_id = stackit_resourcemanager_project.this.project_id network_id = stackit_network.lan[0].network_id - ipv4 = var.firewall.lan_ip + ipv4 = coalesce(var.firewall.lan_ip, cidrhost(var.firewall.lan_network_range, 4)) security = false } \ No newline at end of file diff --git a/modules/connectivity/variables.tf b/modules/connectivity/variables.tf index b35ed6c..21ec723 100644 --- a/modules/connectivity/variables.tf +++ b/modules/connectivity/variables.tf @@ -16,12 +16,12 @@ variable "firewall" { type = object({ zone = string flavor = string - lan_ip = string - lan_prefix = string - wan_ip = string - wan_prefix = string + lan_network_range = string + wan_network_range = string + lan_ip = optional(string, null) + wan_ip = optional(string, null) }) - description = "pfSense firewall configuration. Set to null to skip firewall deployment (network area and routing are still created). lan_prefix and wan_prefix must be CIDRs within the network area range that contain the respective IPs." + description = "pfSense firewall configuration. Set to null to skip firewall deployment (network area and routing are still created). lan_network_range and wan_network_range must be CIDRs within the network area range. lan_ip and wan_ip are optional; when omitted, the 5th address of the respective prefix is used (STACKIT reserves the first usable address as the gateway)." default = null validation { diff --git a/modules/governance/1-rm-folders.tf b/modules/governance/1-rm-folders.tf index 3e5f758..df3800e 100644 --- a/modules/governance/1-rm-folders.tf +++ b/modules/governance/1-rm-folders.tf @@ -32,7 +32,11 @@ resource "stackit_resourcemanager_folder" "this" { name = each.value.name parent_container_id = var.organization_id owner_email = var.owner_email - labels = length(var.labels) > 0 ? var.labels : null # provider bug: empty map becomes null after apply + # labels = length(var.labels) > 0 ? var.labels : null # provider bug: empty map becomes null after apply + + lifecycle { + ignore_changes = [labels] + } } ################################ diff --git a/modules/landing-zone/3-network.tf b/modules/landing-zone/3-network.tf index f95d564..57ff21e 100644 --- a/modules/landing-zone/3-network.tf +++ b/modules/landing-zone/3-network.tf @@ -2,7 +2,8 @@ ## ROUTING ## ############# resource "stackit_routing_table" "this" { - count = var.corporate ? 1 : 0 + count = var.corporate && var.firewall_next_hop_ip != null ? 1 : 0 + organization_id = var.organization_id network_area_id = var.network_area_id name = var.naming_pattern @@ -12,11 +13,11 @@ resource "stackit_routing_table" "this" { } resource "stackit_routing_table_route" "this" { - count = var.corporate ? 1 : 0 - routing_table_id = stackit_routing_table.this[0].routing_table_id + count = var.corporate && var.firewall_next_hop_ip != null ? 1 : 0 - organization_id = var.organization_id - network_area_id = var.network_area_id + routing_table_id = stackit_routing_table.this[0].routing_table_id + organization_id = var.organization_id + network_area_id = var.network_area_id destination = { type = "cidrv4" @@ -35,14 +36,14 @@ resource "stackit_routing_table_route" "this" { ## NETWORK ## ############# resource "stackit_network" "this" { - count = var.corporate ? 1 : 0 - project_id = stackit_resourcemanager_project.this.project_id + count = var.corporate ? 1 : 0 name = "${var.naming_pattern}-routed" + project_id = stackit_resourcemanager_project.this.project_id ipv4_prefix_length = var.network_prefix_length routed = true ipv4_nameservers = var.ipv4_nameservers - routing_table_id = stackit_routing_table.this[0].routing_table_id + routing_table_id = var.firewall_next_hop_ip != null ? stackit_routing_table.this[0].routing_table_id : null labels = local.labels } diff --git a/modules/landing-zone/outputs.tf b/modules/landing-zone/outputs.tf index 5ed1872..64741fc 100644 --- a/modules/landing-zone/outputs.tf +++ b/modules/landing-zone/outputs.tf @@ -21,4 +21,14 @@ output "dns_zone_dns_name" { output "dns_zone_id" { description = "The ID of the landing zone's child DNS zone." value = var.dns_zone_name != null ? stackit_dns_zone.this[0].zone_id : null +} + +output "connected_network_area_id" { + description = "The ID of the connected network area." + value = try(var.network_area_id, null) +} + +output "landing_zone_type" { + description = "The type of the landing zone, either 'corporate' or 'public'." + value = var.corporate ? "corporate" : "public" } \ No newline at end of file From 301b63b94f511d92192976198b40fb10fcc536c3 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Mon, 4 May 2026 17:43:14 +0200 Subject: [PATCH 05/30] restructured Co-authored-by: Copilot --- .../scripts}/generate_example_architecture.py | 0 .../scripts}/validate_stackit_flavors.py | 0 examples/01-standalone/main.tf | 91 ---------- examples/01-standalone/outputs.tf | 33 ---- examples/01-standalone/terraform.tfvars | 53 ------ examples/01-standalone/variables.tf | 83 --------- examples/02-hub-spoke/README.md | 64 ------- examples/02-hub-spoke/backend.tf | 14 -- examples/02-hub-spoke/pfsense.qcow2 | 1 - examples/02-hub-spoke/providers.tf | 17 -- examples/02-hub-spoke/terraform.tf | 18 -- examples/02-hub-spoke/terraform.tfvars | 138 --------------- examples/02-hub-spoke/variables.tf | 136 --------------- {examples/01-standalone => src}/README.md | 0 {examples/01-standalone => src}/backend.tf | 0 src/config/hub-and-spoke-firewall.tfvars | 116 +++++++++++++ src/config/hub-and-spoke.tfvars | 108 ++++++++++++ src/config/standalone.tfvars | 73 ++++++++ {examples/02-hub-spoke => src}/main.tf | 64 +++---- .../modules}/connectivity/1-network-area.tf | 8 + .../modules}/connectivity/2-project.tf | 0 .../connectivity/3-external-network.tf | 0 .../connectivity/4-internal-network.tf | 0 .../modules}/connectivity/5-firewall.tf | 0 .../modules}/connectivity/README.md | 0 .../modules}/connectivity/outputs.tf | 0 .../modules}/connectivity/terraform.tf | 0 .../modules}/connectivity/variables.tf | 0 {modules => src/modules}/devops/1-project.tf | 0 {modules => src/modules}/devops/2-git.tf | 0 {modules => src/modules}/devops/README.md | 0 {modules => src/modules}/devops/outputs.tf | 0 {modules => src/modules}/devops/terraform.tf | 0 {modules => src/modules}/devops/variables.tf | 0 .../modules}/governance/1-rm-folders.tf | 0 .../modules}/governance/2-custom-roles.tf | 0 .../governance/3-organization-roles.tf | 0 {modules => src/modules}/governance/README.md | 0 .../modules}/governance/outputs.tf | 0 .../modules/governance}/terraform.tf | 4 + .../modules}/governance/variables.tf | 0 .../modules}/landing-zone/1-project.tf | 0 .../modules}/landing-zone/2-rbac.tf | 0 .../modules}/landing-zone/3-network.tf | 0 .../landing-zone/4-secrets-manager.tf | 0 .../modules}/landing-zone/5-bucket.tf | 0 .../landing-zone/6-service-account.tf | 0 .../modules}/landing-zone/7-dns-zone.tf | 0 .../modules}/landing-zone/README.md | 0 .../modules}/landing-zone/outputs.tf | 0 .../modules}/landing-zone/terraform.tf | 0 .../modules}/landing-zone/variables.tf | 0 .../modules}/management/1-project.tf | 0 .../modules}/management/2-secrets-manager.tf | 0 .../modules}/management/3-bucket.tf | 0 .../modules}/management/4-service-account.tf | 0 .../modules}/management/5-observability.tf | 0 {modules => src/modules}/management/README.md | 0 .../modules}/management/outputs.tf | 0 .../modules}/management/terraform.tf | 0 .../modules}/management/variables.tf | 0 {modules => src/modules}/sandboxes/README.md | 0 {modules => src/modules}/sandboxes/main.tf | 0 {modules => src/modules}/sandboxes/outputs.tf | 0 .../modules/sandboxes}/terraform.tf | 0 .../modules}/sandboxes/variables.tf | 0 {examples/02-hub-spoke => src}/outputs.tf | 6 +- {examples/01-standalone => src}/providers.tf | 0 {examples/01-standalone => src}/terraform.tf | 0 src/tests/hub_spoke.tftest.hcl | 145 ++++++++++++++++ src/tests/hub_spoke_firewall.tftest.hcl | 108 ++++++++++++ src/tests/standalone.tftest.hcl | 89 ++++++++++ src/variables.tf | 164 ++++++++++++++++++ 73 files changed, 841 insertions(+), 692 deletions(-) rename {scripts => docs/scripts}/generate_example_architecture.py (100%) rename {scripts => docs/scripts}/validate_stackit_flavors.py (100%) delete mode 100644 examples/01-standalone/main.tf delete mode 100644 examples/01-standalone/outputs.tf delete mode 100644 examples/01-standalone/terraform.tfvars delete mode 100644 examples/01-standalone/variables.tf delete mode 100644 examples/02-hub-spoke/README.md delete mode 100644 examples/02-hub-spoke/backend.tf delete mode 100644 examples/02-hub-spoke/pfsense.qcow2 delete mode 100644 examples/02-hub-spoke/providers.tf delete mode 100644 examples/02-hub-spoke/terraform.tf delete mode 100644 examples/02-hub-spoke/terraform.tfvars delete mode 100644 examples/02-hub-spoke/variables.tf rename {examples/01-standalone => src}/README.md (100%) rename {examples/01-standalone => src}/backend.tf (100%) create mode 100644 src/config/hub-and-spoke-firewall.tfvars create mode 100644 src/config/hub-and-spoke.tfvars create mode 100644 src/config/standalone.tfvars rename {examples/02-hub-spoke => src}/main.tf (56%) rename {modules => src/modules}/connectivity/1-network-area.tf (70%) rename {modules => src/modules}/connectivity/2-project.tf (100%) rename {modules => src/modules}/connectivity/3-external-network.tf (100%) rename {modules => src/modules}/connectivity/4-internal-network.tf (100%) rename {modules => src/modules}/connectivity/5-firewall.tf (100%) rename {modules => src/modules}/connectivity/README.md (100%) rename {modules => src/modules}/connectivity/outputs.tf (100%) rename {modules => src/modules}/connectivity/terraform.tf (100%) rename {modules => src/modules}/connectivity/variables.tf (100%) rename {modules => src/modules}/devops/1-project.tf (100%) rename {modules => src/modules}/devops/2-git.tf (100%) rename {modules => src/modules}/devops/README.md (100%) rename {modules => src/modules}/devops/outputs.tf (100%) rename {modules => src/modules}/devops/terraform.tf (100%) rename {modules => src/modules}/devops/variables.tf (100%) rename {modules => src/modules}/governance/1-rm-folders.tf (100%) rename {modules => src/modules}/governance/2-custom-roles.tf (100%) rename {modules => src/modules}/governance/3-organization-roles.tf (100%) rename {modules => src/modules}/governance/README.md (100%) rename {modules => src/modules}/governance/outputs.tf (100%) rename {modules/sandboxes => src/modules/governance}/terraform.tf (66%) rename {modules => src/modules}/governance/variables.tf (100%) rename {modules => src/modules}/landing-zone/1-project.tf (100%) rename {modules => src/modules}/landing-zone/2-rbac.tf (100%) rename {modules => src/modules}/landing-zone/3-network.tf (100%) rename {modules => src/modules}/landing-zone/4-secrets-manager.tf (100%) rename {modules => src/modules}/landing-zone/5-bucket.tf (100%) rename {modules => src/modules}/landing-zone/6-service-account.tf (100%) rename {modules => src/modules}/landing-zone/7-dns-zone.tf (100%) rename {modules => src/modules}/landing-zone/README.md (100%) rename {modules => src/modules}/landing-zone/outputs.tf (100%) rename {modules => src/modules}/landing-zone/terraform.tf (100%) rename {modules => src/modules}/landing-zone/variables.tf (100%) rename {modules => src/modules}/management/1-project.tf (100%) rename {modules => src/modules}/management/2-secrets-manager.tf (100%) rename {modules => src/modules}/management/3-bucket.tf (100%) rename {modules => src/modules}/management/4-service-account.tf (100%) rename {modules => src/modules}/management/5-observability.tf (100%) rename {modules => src/modules}/management/README.md (100%) rename {modules => src/modules}/management/outputs.tf (100%) rename {modules => src/modules}/management/terraform.tf (100%) rename {modules => src/modules}/management/variables.tf (100%) rename {modules => src/modules}/sandboxes/README.md (100%) rename {modules => src/modules}/sandboxes/main.tf (100%) rename {modules => src/modules}/sandboxes/outputs.tf (100%) rename {modules/governance => src/modules/sandboxes}/terraform.tf (100%) rename {modules => src/modules}/sandboxes/variables.tf (100%) rename {examples/02-hub-spoke => src}/outputs.tf (87%) rename {examples/01-standalone => src}/providers.tf (100%) rename {examples/01-standalone => src}/terraform.tf (100%) create mode 100644 src/tests/hub_spoke.tftest.hcl create mode 100644 src/tests/hub_spoke_firewall.tftest.hcl create mode 100644 src/tests/standalone.tftest.hcl create mode 100644 src/variables.tf diff --git a/scripts/generate_example_architecture.py b/docs/scripts/generate_example_architecture.py similarity index 100% rename from scripts/generate_example_architecture.py rename to docs/scripts/generate_example_architecture.py diff --git a/scripts/validate_stackit_flavors.py b/docs/scripts/validate_stackit_flavors.py similarity index 100% rename from scripts/validate_stackit_flavors.py rename to docs/scripts/validate_stackit_flavors.py diff --git a/examples/01-standalone/main.tf b/examples/01-standalone/main.tf deleted file mode 100644 index bb39d20..0000000 --- a/examples/01-standalone/main.tf +++ /dev/null @@ -1,91 +0,0 @@ -################ -## GOVERNANCE ## -################ - -module "governance" { - source = "../../modules/governance" - - owner_email = var.owner_email - organization_id = var.organization_id - labels = var.labels - organization_owners = var.organization_owners - organization_auditors = var.organization_auditors - - rm_folders = { - platform = { - name = "Platform" - owner_emails = [] - reader_emails = [] - } - landing_zones = { - name = "Landing Zones" - owner_emails = [] - reader_emails = [] - } - sandboxes = { - name = "Sandboxes" - owner_emails = [] - reader_emails = [] - } - } -} - -################ -## MANAGEMENT ## -################ - -module "management" { - source = "../../modules/management" - - owner_email = var.owner_email - naming_pattern = "${var.company_code}-pltfm-mgmt-prod" - parent_container_id = module.governance.folder_container_ids["platform"] - organization_id = var.organization_id - labels = var.labels -} - -############ -## DEVOPS ## -############ - -module "devops" { - source = "../../modules/devops" - count = var.devops_enabled ? 1 : 0 - - owner_email = var.owner_email - naming_pattern = "${var.company_code}-pltfm-devops-prod" - company_name = var.company_name - parent_container_id = module.governance.folder_container_ids["platform"] - labels = var.labels -} - -############### -## SANDBOXES ## -############### - -module "sandboxes" { - source = "../../modules/sandboxes" - count = length(var.sandboxes) > 0 ? 1 : 0 - - naming_prefix = "${var.company_code}-sbx" - parent_container_id = module.governance.folder_container_ids["sandboxes"] - sandboxes = var.sandboxes -} - -################### -## LANDING ZONES ## -################### - -module "landing_zone" { - source = "../../modules/landing-zone" - for_each = var.landing_zones - - organization_id = var.organization_id - parent_container_id = module.governance.folder_container_ids["landing_zones"] - naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" - owner_email = each.value.owner_email - labels = var.labels - role_assignments = each.value.role_assignments - network_prefix_length = each.value.network_prefix_length - custom_roles = each.value.custom_roles -} diff --git a/examples/01-standalone/outputs.tf b/examples/01-standalone/outputs.tf deleted file mode 100644 index f820abc..0000000 --- a/examples/01-standalone/outputs.tf +++ /dev/null @@ -1,33 +0,0 @@ -############# -## OUTPUTS ## -############# - -output "governance_folder_ids" { - description = "Map of governance folder names to their container IDs." - value = module.governance.folder_container_ids -} - -output "devops_project_id" { - description = "The project ID of the DevOps project." - value = length(module.devops) > 0 ? module.devops[0].project_id : null -} - -output "management_project_id" { - description = "The project ID of the Management project." - value = module.management.project_id -} - -output "sandbox_projects" { - description = "The created sandbox projects." - value = length(module.sandboxes) > 0 ? module.sandboxes[0].projects : {} -} - -output "landing_zone_projects" { - description = "Map of landing zone project IDs." - value = { - for k, v in module.landing_zone : k => { - project_id = v.project_id - project_name = v.project_name - } - } -} \ No newline at end of file diff --git a/examples/01-standalone/terraform.tfvars b/examples/01-standalone/terraform.tfvars deleted file mode 100644 index 857f5e1..0000000 --- a/examples/01-standalone/terraform.tfvars +++ /dev/null @@ -1,53 +0,0 @@ -# Email of the technical owner registered in STACKIT -owner_email = "platform-team@example.com" - -# Company name used for folder naming in the resource manager -company_name = "Example Corp" - -# Short company code used as prefix in resource naming (e.g. project names, service accounts) -company_code = "exc" - -# Root organization container ID from STACKIT resource manager -organization_id = "org-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - -region = "eu01" - -# Labels applied to all resources for cost tracking / filtering -labels = { - managed_by = "opentofu" -} - -# Users with full organization-level owner permissions -organization_owners = [ - "org-owner@example.com" -] - -# Users with read-only audit access at the organization level -organization_auditors = [ - "auditor@example.com" -] - -# Sandbox projects for experimentation / PoCs -sandboxes = [ - { - project_name = "Sandbox Team Alpha" - project_owner_email = "alpha-lead@example.com" - owner_emails = ["dev1@example.com", "dev2@example.com"] - } -] - -# Landing zones keyed by a unique identifier -landing_zones = { - "fe-dev" = { - project_code = "fe" - owner_email = "frontend-team@example.com" - env = "dev" - - role_assignments = [ - { - role = "project.owner" - subject = "frontend-lead@example.com" - } - ] - } -} diff --git a/examples/01-standalone/variables.tf b/examples/01-standalone/variables.tf deleted file mode 100644 index d310265..0000000 --- a/examples/01-standalone/variables.tf +++ /dev/null @@ -1,83 +0,0 @@ -############### -## VARIABLES ## -############### - -variable "owner_email" { - type = string - description = "Email address of the owner. Required for STACKIT resource manager." -} - -variable "company_name" { - type = string - description = "Name of the company." -} - -variable "company_code" { - type = string - description = "Company code used in resource naming conventions." -} - -variable "organization_id" { - type = string - description = "Container ID of the root organization." -} - -variable "region" { - type = string - description = "STACKIT region for regional resources." - default = "eu01" -} - -variable "labels" { - type = map(string) - description = "Additional labels to apply to all resources." - default = {} -} - -variable "organization_owners" { - type = list(string) - description = "List of organization owners." - default = [] -} - -variable "organization_auditors" { - type = list(string) - description = "List of organization auditors." - default = [] -} - -variable "devops_enabled" { - type = bool - description = "Whether to deploy the DevOps module (Git repository project)." - default = true -} - -variable "sandboxes" { - type = list(object({ - project_name = string - owner_emails = optional(list(string)) - project_owner_email = string - })) - description = "List of sandboxes to create." - default = [] -} - -variable "landing_zones" { - type = map(object({ - project_code = string - owner_email = string - env = optional(string, "dev") - role_assignments = optional(list(object({ - role = string - subject = string - })), []) - network_prefix_length = optional(number, null) - custom_roles = optional(list(object({ - name = string - description = string - permissions = list(string) - })), []) - })) - description = "Map of landing zones to create (public, without network area)." - default = {} -} \ No newline at end of file diff --git a/examples/02-hub-spoke/README.md b/examples/02-hub-spoke/README.md deleted file mode 100644 index 449608d..0000000 --- a/examples/02-hub-spoke/README.md +++ /dev/null @@ -1,64 +0,0 @@ - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | 0.88.0 | - -## Providers - -No providers. - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [connectivity\_global](#module\_connectivity\_global) | ../../modules/connectivity-global | n/a | -| [connectivity\_regional](#module\_connectivity\_regional) | ../../modules/connectivity-regional | n/a | -| [devops](#module\_devops) | ../../modules/devops | n/a | -| [governance](#module\_governance) | ../../modules/governance | n/a | -| [landing\_zone](#module\_landing\_zone) | ../../modules/landing-zone | n/a | -| [management](#module\_management) | ../../modules/management | n/a | -| [sandboxes](#module\_sandboxes) | ../../modules/sandboxes | n/a | - -## Resources - -No resources. - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [company\_code](#input\_company\_code) | Company code used in resource naming conventions. | `string` | n/a | yes | -| [company\_name](#input\_company\_name) | Name of the company. | `string` | n/a | yes | -| [connectivity\_regional\_network\_area](#input\_connectivity\_regional\_network\_area) | Name key of the network area (from network\_areas) to use for the regional connectivity project. | `string` | n/a | yes | -| [connectivity\_vnet\_range](#input\_connectivity\_vnet\_range) | CIDR range for the connectivity project VNet. | `string` | `"10.0.0.0/24"` | no | -| [firewall\_flavor](#input\_firewall\_flavor) | Firewall VM flavor. | `string` | `"c1.2"` | no | -| [firewall\_ip](#input\_firewall\_ip) | Static IP address for the firewall LAN interface. | `string` | `"10.0.0.220"` | no | -| [firewall\_zone](#input\_firewall\_zone) | STACKIT Availability Zone for the firewall VM. | `string` | `"eu01-m"` | no | -| [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no | -| [landing\_zone\_admins](#input\_landing\_zone\_admins) | List of landing zone administrators. | `list(string)` | `[]` | no | -| [landing\_zones](#input\_landing\_zones) | Map of landing zones to create. Set corporate = true for network area connectivity, false for public. |
map(object({
project_name = string
project_code = string
owner_email = string
# Set to true for corporate landing zones (connected to network area), false for public
corporate = optional(bool, true)
env = optional(string, "dev")
role_assignments = optional(list(object({
role = string
subject = string
})), [])
network_prefix_length = optional(number, null)
custom_roles = optional(list(object({
name = string
description = string
permissions = list(string)
})), [])
kubernetes_clusters = optional(map(object({
kubernetes_version = string
enable_kubernetes_version_updates = optional(bool, true)
enable_machine_image_version_updates = optional(bool, true)
hibernations = optional(list(object({
start = string
end = string
timezone = optional(string, "Europe/Berlin")
})), [])
node_pools = list(object({
name = string
machine_type = string
availability_zones = list(string)
os_version_min = optional(string)
minimum = number
maximum = number
max_surge = optional(number)
max_unavailable = optional(number)
labels = optional(map(string))
taints = optional(list(object({
key = string
value = string
effect = string
})))
}))
extensions = optional(object({
acl = optional(object({
allowed_cidrs = list(string)
enabled = bool
}))
dns = optional(object({
enabled = bool
zones = optional(list(string))
}))
observability = optional(object({
enabled = bool
instance_id = optional(string)
}))
}))
})), {})
}))
| `{}` | no | -| [network\_areas](#input\_network\_areas) | List of network areas to create with their IP ranges and configuration. |
list(object({
name = string
network_ranges = list(object({ prefix = string }))
transfer_network_range = string
max_prefix_length = optional(number, 28)
min_prefix_length = optional(number, 24)
default_prefix_length = optional(number, 28)
default_nameservers = optional(list(string), null)
}))
| n/a | yes | -| [organization\_auditors](#input\_organization\_auditors) | List of organization auditors. | `list(string)` | `[]` | no | -| [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes | -| [organization\_owners](#input\_organization\_owners) | List of organization owners. | `list(string)` | `[]` | no | -| [owner\_email](#input\_owner\_email) | Email address of the owner. Required for STACKIT resource manager. | `string` | n/a | yes | -| [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no | -| [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [connectivity\_global\_network\_area\_ids](#output\_connectivity\_global\_network\_area\_ids) | Map of network area names to their IDs. | -| [connectivity\_regional\_pfsense\_public\_ip](#output\_connectivity\_regional\_pfsense\_public\_ip) | The public IP of the pfSense firewall. | -| [connectivity\_regional\_pfsense\_wan\_ip](#output\_connectivity\_regional\_pfsense\_wan\_ip) | The internal WAN IP of the pfSense firewall (used as next hop). | -| [connectivity\_regional\_project\_id](#output\_connectivity\_regional\_project\_id) | The project ID of the regional connectivity project. | -| [devops\_project\_id](#output\_devops\_project\_id) | The project ID of the DevOps project. | -| [governance\_folder\_ids](#output\_governance\_folder\_ids) | Map of governance folder names to their container IDs. | -| [landing\_zone\_projects](#output\_landing\_zone\_projects) | Map of landing zone project IDs. | -| [management\_project\_id](#output\_management\_project\_id) | The project ID of the Management project. | -| [sandbox\_projects](#output\_sandbox\_projects) | The created sandbox projects. | - \ No newline at end of file diff --git a/examples/02-hub-spoke/backend.tf b/examples/02-hub-spoke/backend.tf deleted file mode 100644 index c9266ff..0000000 --- a/examples/02-hub-spoke/backend.tf +++ /dev/null @@ -1,14 +0,0 @@ -# terraform { -# backend "s3" { -# bucket = "" -# endpoints = { -# s3 = "https://object.storage.eu01.onstackit.cloud" -# } -# key = "terraform.tfstate" -# region = "eu01" -# skip_credentials_validation = true -# skip_region_validation = true -# skip_requesting_account_id = true -# skip_s3_checksum = true -# } -# } diff --git a/examples/02-hub-spoke/pfsense.qcow2 b/examples/02-hub-spoke/pfsense.qcow2 deleted file mode 100644 index 311c8dd..0000000 --- a/examples/02-hub-spoke/pfsense.qcow2 +++ /dev/null @@ -1 +0,0 @@ -PLACEHOLDER \ No newline at end of file diff --git a/examples/02-hub-spoke/providers.tf b/examples/02-hub-spoke/providers.tf deleted file mode 100644 index 0dda673..0000000 --- a/examples/02-hub-spoke/providers.tf +++ /dev/null @@ -1,17 +0,0 @@ -provider "stackit" { - default_region = var.region - enable_beta_resources = true - experiments = ["iam", "routing-tables", "network"] -} - -provider "vault" { - address = "https://prod.sm.eu01.stackit.cloud" - skip_child_token = true - - auth_login_userpass { - username = module.management.secretsmanager_username - password = module.management.secretsmanager_password - } -} - -provider "time" {} \ No newline at end of file diff --git a/examples/02-hub-spoke/terraform.tf b/examples/02-hub-spoke/terraform.tf deleted file mode 100644 index 0cdc026..0000000 --- a/examples/02-hub-spoke/terraform.tf +++ /dev/null @@ -1,18 +0,0 @@ -terraform { - required_version = ">= 1.10" - - required_providers { - stackit = { - source = "stackitcloud/stackit" - version = "0.93.0" - } - time = { - source = "hashicorp/time" - version = "0.13.1" - } - vault = { - source = "hashicorp/vault" - version = "5.7.0" - } - } -} \ No newline at end of file diff --git a/examples/02-hub-spoke/terraform.tfvars b/examples/02-hub-spoke/terraform.tfvars deleted file mode 100644 index 9eee37d..0000000 --- a/examples/02-hub-spoke/terraform.tfvars +++ /dev/null @@ -1,138 +0,0 @@ -###################### -## GENERAL SETTINGS ## -###################### - -# Email of the technical owner registered in STACKIT -owner_email = "example@company.com" - -# Company name used for folder naming in the resource manager -company_name = "Example Corp" - -# Short company code used as prefix in resource naming (e.g. project names, service accounts) -company_code = "exc" - -# Root organization container ID from STACKIT resource manager -organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" - -region = "eu01" - -# Labels applied to all resources, max. 64 characters -labels = { - managed_by = "opentofu" - environment = "production" -} - -# Users with full organization-level owner permissions -# organization_owners = [ -# "org-owner@example.com" -# ] - -# # Users with read-only audit access at the organization level -# organization_auditors = [ -# "auditor@example.com" -# ] - -################## -## CONNECTIVITY ## -################## - -# DNS zones managed in the connectivity project -dns_zones = { - "example-corp" = { - dns_name = "example-corp.stackit.run" - } -} - -# Network area configuration for the connectivity hub -network_area = { - ranges = ["10.0.0.0/16"] - transfer_network = "10.255.0.0/24" - min_prefix_length = 24 - max_prefix_length = 28 - default_prefix_length = 25 -} - -# Delete the variable to skip firewall deployment (network area and routing still created) -firewall = { - zone = "eu01-m" - flavor = "c1.2" - lan_network_range = "10.0.0.0/28" - wan_network_range = "10.0.0.16/28" -} - -############### -## SANDBOXES ## -############### - -# Sandbox projects for experimentation / PoCs -# sandboxes = [ -# { -# project_name = "Sandbox Team Alpha" -# project_owner_email = "alpha-lead@example.com" -# owner_emails = ["dev1@example.com", "dev2@example.com"] -# }, -# ] - -################### -## LANDING ZONES ## -################### - -# Landing zones keyed by a unique identifier -# Set corporate = true for network area connectivity, false for public internet -landing_zones = { - # "app-backend" = { - # project_name = "Backend Services" - # project_code = "be" - # owner_email = "backend-team@example.com" - # env = "prod" - # corporate = true - - # # Subnet size assigned from the network area (/25 = 128 addresses) - # network_prefix_length = 25 - - # role_assignments = [ - # { - # role = "project.owner" - # subject = "backend-lead@example.com" - # }, - # { - # role = "project.member" - # subject = "backend-dev@example.com" - # } - # ] - - # custom_roles = [ - # { - # name = "deployer" - # description = "Can deploy workloads" - # permissions = ["project.resources.read", "project.resources.write"] - # } - # ] - # } - - "data-platform" = { - project_name = "Data Platform" - project_code = "data" - owner_email = "matthias.hauber@prodyna.com" - env = "prod" - corporate = true - - network_prefix_length = 24 - } - - # Public landing zone โ€” no network area, uses STACKIT's default public networking - "external-api" = { - project_name = "External API Gateway" - project_code = "api" - owner_email = "matthias.hauber@prodyna.com" - env = "prod" - corporate = false - - # role_assignments = [ - # { - # role = "project.owner" - # subject = "api-lead@example.com" - # } - # ] - } -} diff --git a/examples/02-hub-spoke/variables.tf b/examples/02-hub-spoke/variables.tf deleted file mode 100644 index a2e7f20..0000000 --- a/examples/02-hub-spoke/variables.tf +++ /dev/null @@ -1,136 +0,0 @@ -############### -## VARIABLES ## -############### - -variable "owner_email" { - type = string - description = "Email address of the owner. Required for STACKIT resource manager." -} - -variable "company_name" { - type = string - description = "Name of the company." -} - -variable "company_code" { - type = string - description = "Company code used in resource naming conventions." -} - -variable "organization_id" { - type = string - description = "Container ID of the root organization." -} - -variable "region" { - type = string - description = "STACKIT region for regional resources." - default = "eu01" -} - -variable "labels" { - type = map(string) - description = "Additional labels to apply to all resources." - default = {} -} - -variable "organization_owners" { - type = list(string) - description = "List of organization owners." - default = [] -} - -variable "organization_auditors" { - type = list(string) - description = "List of organization auditors." - default = [] -} - -variable "devops_enabled" { - type = bool - description = "Whether to deploy the DevOps module (Git repository project)." - default = true -} - -variable "dns_zones" { - type = map(object({ - dns_name = string - name = optional(string, null) - contact_email = optional(string, null) - type = optional(string, "primary") - acl = optional(string, null) - description = optional(string, null) - default_ttl = optional(number, 3600) - })) - description = "Map of DNS zone keys to DNS zone configuration. Name defaults to dns_name if not set." - default = {} -} - -############################# -## CONNECTIVITY - REGIONAL ## -############################# - -variable "network_area" { - type = object({ - ranges = list(string) - transfer_network = string - min_prefix_length = optional(number, 24) - max_prefix_length = optional(number, 28) - default_prefix_length = optional(number, 28) - }) - description = "Network area configuration including IP ranges, transfer network, and prefix length settings." -} - -variable "firewall" { - type = object({ - zone = string - flavor = string - lan_network_range = string - wan_network_range = string - lan_ip = optional(string, null) - wan_ip = optional(string, null) - }) - description = "pfSense firewall configuration. Set to null to skip firewall deployment. lan_ip/wan_ip default to the 5th address of the respective prefix." - default = null -} - -############### -## SANDBOXES ## -############### - -variable "sandboxes" { - type = list(object({ - project_name = string - owner_emails = optional(list(string)) - project_owner_email = string - })) - description = "List of sandboxes to create." - default = [] -} - -################## -## LANDING ZONE ## -################## - -variable "landing_zones" { - type = map(object({ - project_name = string - project_code = string - owner_email = string - # Set to true for corporate landing zones (connected to network area), false for public - corporate = optional(bool, true) - env = optional(string, "dev") - role_assignments = optional(list(object({ - role = string - subject = string - })), []) - network_prefix_length = optional(number, null) - custom_roles = optional(list(object({ - name = string - description = string - permissions = list(string) - })), []) - })) - description = "Map of landing zones to create. Set corporate = true for network area connectivity, false for public." - default = {} -} diff --git a/examples/01-standalone/README.md b/src/README.md similarity index 100% rename from examples/01-standalone/README.md rename to src/README.md diff --git a/examples/01-standalone/backend.tf b/src/backend.tf similarity index 100% rename from examples/01-standalone/backend.tf rename to src/backend.tf diff --git a/src/config/hub-and-spoke-firewall.tfvars b/src/config/hub-and-spoke-firewall.tfvars new file mode 100644 index 0000000..60c47c2 --- /dev/null +++ b/src/config/hub-and-spoke-firewall.tfvars @@ -0,0 +1,116 @@ +###################### +## GENERAL SETTINGS ## +###################### + +# Email of the technical owner registered in STACKIT +owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + +# Company name used for folder naming in the resource manager +company_name = "Example Corp" + +# Short company code used as prefix in resource naming (e.g. project names, service accounts) +company_code = "exc" + +# Root organization container ID from STACKIT resource manager +organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + +region = "eu01" + +# Labels applied to all resources, max. 64 characters +labels = { + managed_by = "opentofu" +} + +# # Users with full organization-level owner permissions +# organization_owners = [ +# "org-owner@example.com" +# ] + +# # Users with read-only audit access at the organization level +# organization_auditors = [ +# "auditor@example.com" +# ] + +################## +## CONNECTIVITY ## +################## + +connectivity = { + # DNS zones managed in the connectivity project + dns_zones = { + "example-corp" = { + dns_name = "example-corp.stackit.run" + } + } + + # Network area configuration for the connectivity hub + network_area = { + ranges = ["10.0.0.0/16"] + transfer_network = "10.255.0.0/24" + min_prefix_length = 24 + max_prefix_length = 28 + default_prefix_length = 25 + } + + # Delete the variable to skip firewall deployment (network area and routing still created) + firewall = { + zone = "eu01-m" + flavor = "c1.2" + lan_network_range = "10.0.0.0/28" + wan_network_range = "10.0.0.16/28" + } +} + +############ +## DEVOPS ## +############ + +devops = { + git_flavor = "git-10" + allowed_network_ranges = ["0.0.0.0/0"] +} + +############### +## SANDBOXES ## +############### + +# Sandbox projects for experimentation / PoCs +sandboxes = [ + { + project_name = "Sandbox Team Alpha" + project_owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + } +] + +################### +## LANDING ZONES ## +################### + +landing_zones = { + "corp-exmpl" = { + project_name = "Data Platform" + project_code = "data" + owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + env = "prod" + + # Set corporate = true for network area connectivity, false for public internet + corporate = true + network_prefix_length = 24 + } + + # Public landing zone โ€” no network area, uses STACKIT's default public networking + "public-exmpl" = { + project_name = "External API Gateway" + project_code = "api" + owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + env = "prod" + corporate = false + + # role_assignments = [ + # { + # role = "project.owner" + # subject = "api-lead@example.com" + # } + # ] + } +} \ No newline at end of file diff --git a/src/config/hub-and-spoke.tfvars b/src/config/hub-and-spoke.tfvars new file mode 100644 index 0000000..12dce87 --- /dev/null +++ b/src/config/hub-and-spoke.tfvars @@ -0,0 +1,108 @@ +###################### +## GENERAL SETTINGS ## +###################### + +# Email of the technical owner registered in STACKIT +owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + +# Company name used for folder naming in the resource manager +company_name = "Example Corp" + +# Short company code used as prefix in resource naming (e.g. project names, service accounts) +company_code = "exc" + +# Root organization container ID from STACKIT resource manager +organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + +region = "eu01" + +# Labels applied to all resources, max. 64 characters +labels = { + managed_by = "opentofu" +} + +# # Users with full organization-level owner permissions +# organization_owners = [ +# "org-owner@example.com" +# ] + +# # Users with read-only audit access at the organization level +# organization_auditors = [ +# "auditor@example.com" +# ] + +################## +## CONNECTIVITY ## +################## + +connectivity = { + # DNS zones managed in the connectivity project + dns_zones = { + "example-corp" = { + dns_name = "example-corp.stackit.run" + } + } + + # Network area configuration for the connectivity hub + network_area = { + ranges = ["10.0.0.0/16"] + transfer_network = "10.255.0.0/24" + min_prefix_length = 24 + max_prefix_length = 28 + default_prefix_length = 25 + } +} + +############ +## DEVOPS ## +############ + +devops = { + git_flavor = "git-10" + allowed_network_ranges = ["0.0.0.0/0"] +} + +############### +## SANDBOXES ## +############### + +# Sandbox projects for experimentation / PoCs +sandboxes = [ + { + project_name = "Sandbox Team Alpha" + project_owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + } +] + +################### +## LANDING ZONES ## +################### + +landing_zones = { + "corp-exmpl" = { + project_name = "Data Platform" + project_code = "data" + owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + env = "prod" + + # Set corporate = true for network area connectivity, false for public internet + corporate = true + network_prefix_length = 24 + } + + # Public landing zone โ€” no network area, uses STACKIT's default public networking + "public-exmpl" = { + project_name = "External API Gateway" + project_code = "api" + owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + env = "prod" + corporate = false + + # role_assignments = [ + # { + # role = "project.owner" + # subject = "api-lead@example.com" + # } + # ] + } +} \ No newline at end of file diff --git a/src/config/standalone.tfvars b/src/config/standalone.tfvars new file mode 100644 index 0000000..d0d9a4d --- /dev/null +++ b/src/config/standalone.tfvars @@ -0,0 +1,73 @@ +###################### +## GENERAL SETTINGS ## +###################### + +# Email of the technical owner registered in STACKIT +owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + +# Company name used for folder naming in the resource manager +company_name = "Example Corp" + +# Short company code used as prefix in resource naming (e.g. project names, service accounts) +company_code = "exc" + +# Root organization container ID from STACKIT resource manager +organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + +region = "eu01" + +# Labels applied to all resources, max. 64 characters +labels = { + managed_by = "opentofu" +} + +# # Users with full organization-level owner permissions +# organization_owners = [ +# "org-owner@example.com" +# ] + +# # Users with read-only audit access at the organization level +# organization_auditors = [ +# "auditor@example.com" +# ] + +############ +## DEVOPS ## +############ + +devops = { + git_flavor = "git-10" + allowed_network_ranges = ["0.0.0.0/0"] +} + +############### +## SANDBOXES ## +############### + +# Sandbox projects for experimentation / PoCs +sandboxes = [ + { + project_name = "Sandbox Team Alpha" + project_owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + } +] + +################### +## LANDING ZONES ## +################### + +landing_zones = { + "public-exmpl" = { + project_name = "External API Gateway" + project_code = "api" + owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + env = "prod" + + # role_assignments = [ + # { + # role = "project.owner" + # subject = "api-lead@example.com" + # } + # ] + } +} \ No newline at end of file diff --git a/examples/02-hub-spoke/main.tf b/src/main.tf similarity index 56% rename from examples/02-hub-spoke/main.tf rename to src/main.tf index 8f4d3ef..cf1ee89 100644 --- a/examples/02-hub-spoke/main.tf +++ b/src/main.tf @@ -3,7 +3,7 @@ ################ module "governance" { - source = "../../modules/governance" + source = "./modules/governance" owner_email = var.owner_email organization_id = var.organization_id @@ -11,28 +11,7 @@ module "governance" { organization_owners = var.organization_owners organization_auditors = var.organization_auditors - rm_folders = { - platform = { - name = "Platform" - owner_emails = [] - reader_emails = [] - } - landing_zones_corporate = { - name = "Landing Zones - Corporate" - owner_emails = [] - reader_emails = [] - } - landing_zones_public = { - name = "Landing Zones - Public" - owner_emails = [] - reader_emails = [] - } - sandboxes = { - name = "Sandboxes" - owner_emails = [] - reader_emails = [] - } - } + rm_folders = var.rm_folders } ################ @@ -40,7 +19,7 @@ module "governance" { ################ module "management" { - source = "../../modules/management" + source = "./modules/management" owner_email = var.owner_email naming_pattern = "${var.company_code}-pltfm-mgmt-prod" @@ -54,16 +33,17 @@ module "management" { ################## module "connectivity" { - source = "../../modules/connectivity" + source = "./modules/connectivity" + count = var.connectivity != null ? 1 : 0 owner_email = var.owner_email naming_pattern = "${var.company_code}-pltfm-hub-prod" parent_container_id = module.governance.folder_container_ids["platform"] organization_id = var.organization_id labels = var.labels - dns_zones = var.dns_zones - network_area = var.network_area - firewall = var.firewall + dns_zones = var.connectivity.dns_zones + network_area = var.connectivity.network_area + firewall = var.connectivity.firewall } ############ @@ -71,14 +51,16 @@ module "connectivity" { ############ module "devops" { - source = "../../modules/devops" - count = var.devops_enabled ? 1 : 0 - - owner_email = var.owner_email - naming_pattern = "${var.company_code}-pltfm-devops-prod" - company_name = var.company_name - parent_container_id = module.governance.folder_container_ids["platform"] - labels = var.labels + source = "./modules/devops" + count = var.devops != null ? 1 : 0 + + owner_email = var.owner_email + naming_pattern = "${var.company_code}-pltfm-devops-prod" + company_name = var.company_name + parent_container_id = module.governance.folder_container_ids["platform"] + labels = var.labels + git_flavor = var.devops.git_flavor + allowed_network_ranges = var.devops.allowed_network_ranges } ############### @@ -86,7 +68,7 @@ module "devops" { ############### module "sandboxes" { - source = "../../modules/sandboxes" + source = "./modules/sandboxes" count = length(var.sandboxes) > 0 ? 1 : 0 naming_prefix = "${var.company_code}-sbx" @@ -99,19 +81,19 @@ module "sandboxes" { ################### module "landing_zone" { - source = "../../modules/landing-zone" + source = "./modules/landing-zone" for_each = var.landing_zones organization_id = var.organization_id parent_container_id = each.value.corporate ? module.governance.folder_container_ids["landing_zones_corporate"] : module.governance.folder_container_ids["landing_zones_public"] naming_pattern = "${var.company_code}-lz-${each.value.project_code}-${each.value.env}" - dns_zone_name = "${each.value.project_code}-${each.value.env}-${var.region}-${split(".", values(module.connectivity.dns_zone_dns_names)[0])[0]}.stackit.run" - network_area_id = each.value.corporate ? module.connectivity.network_area_id : null + dns_zone_name = try("${each.value.project_code}-${each.value.env}-${var.region}-${split(".", values(module.connectivity[0].dns_zone_dns_names)[0])[0]}.stackit.run", null) + network_area_id = each.value.corporate ? try(module.connectivity[0].network_area_id, null) : null corporate = each.value.corporate owner_email = each.value.owner_email labels = var.labels role_assignments = each.value.role_assignments network_prefix_length = each.value.network_prefix_length custom_roles = each.value.custom_roles - firewall_next_hop_ip = var.firewall != null ? module.connectivity.firewall_next_hop_ip : null # if firewall is enabled, pass the next hop IP to the landing zones for route configuration + firewall_next_hop_ip = var.connectivity != null && var.connectivity.firewall != null ? module.connectivity[0].firewall_next_hop_ip : null # if firewall is enabled, pass the next hop IP to the landing zones for route configuration } diff --git a/modules/connectivity/1-network-area.tf b/src/modules/connectivity/1-network-area.tf similarity index 70% rename from modules/connectivity/1-network-area.tf rename to src/modules/connectivity/1-network-area.tf index ab50e8c..dc35915 100644 --- a/modules/connectivity/1-network-area.tf +++ b/src/modules/connectivity/1-network-area.tf @@ -21,3 +21,11 @@ resource "stackit_network_area_region" "this" { default_nameservers = var.network_area.default_nameservers } } + +# This gives STACKIT time to de-register projects that were attached to the network area +# Error: Network area ready for deletion waiting: found non-GenericOpenApiError: network area with id ... has still active projects +resource "time_sleep" "wait_before_network_area_region_destroy" { + destroy_duration = "120s" + + depends_on = [stackit_network_area_region.this] +} diff --git a/modules/connectivity/2-project.tf b/src/modules/connectivity/2-project.tf similarity index 100% rename from modules/connectivity/2-project.tf rename to src/modules/connectivity/2-project.tf diff --git a/modules/connectivity/3-external-network.tf b/src/modules/connectivity/3-external-network.tf similarity index 100% rename from modules/connectivity/3-external-network.tf rename to src/modules/connectivity/3-external-network.tf diff --git a/modules/connectivity/4-internal-network.tf b/src/modules/connectivity/4-internal-network.tf similarity index 100% rename from modules/connectivity/4-internal-network.tf rename to src/modules/connectivity/4-internal-network.tf diff --git a/modules/connectivity/5-firewall.tf b/src/modules/connectivity/5-firewall.tf similarity index 100% rename from modules/connectivity/5-firewall.tf rename to src/modules/connectivity/5-firewall.tf diff --git a/modules/connectivity/README.md b/src/modules/connectivity/README.md similarity index 100% rename from modules/connectivity/README.md rename to src/modules/connectivity/README.md diff --git a/modules/connectivity/outputs.tf b/src/modules/connectivity/outputs.tf similarity index 100% rename from modules/connectivity/outputs.tf rename to src/modules/connectivity/outputs.tf diff --git a/modules/connectivity/terraform.tf b/src/modules/connectivity/terraform.tf similarity index 100% rename from modules/connectivity/terraform.tf rename to src/modules/connectivity/terraform.tf diff --git a/modules/connectivity/variables.tf b/src/modules/connectivity/variables.tf similarity index 100% rename from modules/connectivity/variables.tf rename to src/modules/connectivity/variables.tf diff --git a/modules/devops/1-project.tf b/src/modules/devops/1-project.tf similarity index 100% rename from modules/devops/1-project.tf rename to src/modules/devops/1-project.tf diff --git a/modules/devops/2-git.tf b/src/modules/devops/2-git.tf similarity index 100% rename from modules/devops/2-git.tf rename to src/modules/devops/2-git.tf diff --git a/modules/devops/README.md b/src/modules/devops/README.md similarity index 100% rename from modules/devops/README.md rename to src/modules/devops/README.md diff --git a/modules/devops/outputs.tf b/src/modules/devops/outputs.tf similarity index 100% rename from modules/devops/outputs.tf rename to src/modules/devops/outputs.tf diff --git a/modules/devops/terraform.tf b/src/modules/devops/terraform.tf similarity index 100% rename from modules/devops/terraform.tf rename to src/modules/devops/terraform.tf diff --git a/modules/devops/variables.tf b/src/modules/devops/variables.tf similarity index 100% rename from modules/devops/variables.tf rename to src/modules/devops/variables.tf diff --git a/modules/governance/1-rm-folders.tf b/src/modules/governance/1-rm-folders.tf similarity index 100% rename from modules/governance/1-rm-folders.tf rename to src/modules/governance/1-rm-folders.tf diff --git a/modules/governance/2-custom-roles.tf b/src/modules/governance/2-custom-roles.tf similarity index 100% rename from modules/governance/2-custom-roles.tf rename to src/modules/governance/2-custom-roles.tf diff --git a/modules/governance/3-organization-roles.tf b/src/modules/governance/3-organization-roles.tf similarity index 100% rename from modules/governance/3-organization-roles.tf rename to src/modules/governance/3-organization-roles.tf diff --git a/modules/governance/README.md b/src/modules/governance/README.md similarity index 100% rename from modules/governance/README.md rename to src/modules/governance/README.md diff --git a/modules/governance/outputs.tf b/src/modules/governance/outputs.tf similarity index 100% rename from modules/governance/outputs.tf rename to src/modules/governance/outputs.tf diff --git a/modules/sandboxes/terraform.tf b/src/modules/governance/terraform.tf similarity index 66% rename from modules/sandboxes/terraform.tf rename to src/modules/governance/terraform.tf index 851721e..7932297 100644 --- a/modules/sandboxes/terraform.tf +++ b/src/modules/governance/terraform.tf @@ -6,5 +6,9 @@ terraform { source = "stackitcloud/stackit" version = ">=0.93.0" } + time = { + source = "hashicorp/time" + version = ">= 0.13.0" + } } } \ No newline at end of file diff --git a/modules/governance/variables.tf b/src/modules/governance/variables.tf similarity index 100% rename from modules/governance/variables.tf rename to src/modules/governance/variables.tf diff --git a/modules/landing-zone/1-project.tf b/src/modules/landing-zone/1-project.tf similarity index 100% rename from modules/landing-zone/1-project.tf rename to src/modules/landing-zone/1-project.tf diff --git a/modules/landing-zone/2-rbac.tf b/src/modules/landing-zone/2-rbac.tf similarity index 100% rename from modules/landing-zone/2-rbac.tf rename to src/modules/landing-zone/2-rbac.tf diff --git a/modules/landing-zone/3-network.tf b/src/modules/landing-zone/3-network.tf similarity index 100% rename from modules/landing-zone/3-network.tf rename to src/modules/landing-zone/3-network.tf diff --git a/modules/landing-zone/4-secrets-manager.tf b/src/modules/landing-zone/4-secrets-manager.tf similarity index 100% rename from modules/landing-zone/4-secrets-manager.tf rename to src/modules/landing-zone/4-secrets-manager.tf diff --git a/modules/landing-zone/5-bucket.tf b/src/modules/landing-zone/5-bucket.tf similarity index 100% rename from modules/landing-zone/5-bucket.tf rename to src/modules/landing-zone/5-bucket.tf diff --git a/modules/landing-zone/6-service-account.tf b/src/modules/landing-zone/6-service-account.tf similarity index 100% rename from modules/landing-zone/6-service-account.tf rename to src/modules/landing-zone/6-service-account.tf diff --git a/modules/landing-zone/7-dns-zone.tf b/src/modules/landing-zone/7-dns-zone.tf similarity index 100% rename from modules/landing-zone/7-dns-zone.tf rename to src/modules/landing-zone/7-dns-zone.tf diff --git a/modules/landing-zone/README.md b/src/modules/landing-zone/README.md similarity index 100% rename from modules/landing-zone/README.md rename to src/modules/landing-zone/README.md diff --git a/modules/landing-zone/outputs.tf b/src/modules/landing-zone/outputs.tf similarity index 100% rename from modules/landing-zone/outputs.tf rename to src/modules/landing-zone/outputs.tf diff --git a/modules/landing-zone/terraform.tf b/src/modules/landing-zone/terraform.tf similarity index 100% rename from modules/landing-zone/terraform.tf rename to src/modules/landing-zone/terraform.tf diff --git a/modules/landing-zone/variables.tf b/src/modules/landing-zone/variables.tf similarity index 100% rename from modules/landing-zone/variables.tf rename to src/modules/landing-zone/variables.tf diff --git a/modules/management/1-project.tf b/src/modules/management/1-project.tf similarity index 100% rename from modules/management/1-project.tf rename to src/modules/management/1-project.tf diff --git a/modules/management/2-secrets-manager.tf b/src/modules/management/2-secrets-manager.tf similarity index 100% rename from modules/management/2-secrets-manager.tf rename to src/modules/management/2-secrets-manager.tf diff --git a/modules/management/3-bucket.tf b/src/modules/management/3-bucket.tf similarity index 100% rename from modules/management/3-bucket.tf rename to src/modules/management/3-bucket.tf diff --git a/modules/management/4-service-account.tf b/src/modules/management/4-service-account.tf similarity index 100% rename from modules/management/4-service-account.tf rename to src/modules/management/4-service-account.tf diff --git a/modules/management/5-observability.tf b/src/modules/management/5-observability.tf similarity index 100% rename from modules/management/5-observability.tf rename to src/modules/management/5-observability.tf diff --git a/modules/management/README.md b/src/modules/management/README.md similarity index 100% rename from modules/management/README.md rename to src/modules/management/README.md diff --git a/modules/management/outputs.tf b/src/modules/management/outputs.tf similarity index 100% rename from modules/management/outputs.tf rename to src/modules/management/outputs.tf diff --git a/modules/management/terraform.tf b/src/modules/management/terraform.tf similarity index 100% rename from modules/management/terraform.tf rename to src/modules/management/terraform.tf diff --git a/modules/management/variables.tf b/src/modules/management/variables.tf similarity index 100% rename from modules/management/variables.tf rename to src/modules/management/variables.tf diff --git a/modules/sandboxes/README.md b/src/modules/sandboxes/README.md similarity index 100% rename from modules/sandboxes/README.md rename to src/modules/sandboxes/README.md diff --git a/modules/sandboxes/main.tf b/src/modules/sandboxes/main.tf similarity index 100% rename from modules/sandboxes/main.tf rename to src/modules/sandboxes/main.tf diff --git a/modules/sandboxes/outputs.tf b/src/modules/sandboxes/outputs.tf similarity index 100% rename from modules/sandboxes/outputs.tf rename to src/modules/sandboxes/outputs.tf diff --git a/modules/governance/terraform.tf b/src/modules/sandboxes/terraform.tf similarity index 100% rename from modules/governance/terraform.tf rename to src/modules/sandboxes/terraform.tf diff --git a/modules/sandboxes/variables.tf b/src/modules/sandboxes/variables.tf similarity index 100% rename from modules/sandboxes/variables.tf rename to src/modules/sandboxes/variables.tf diff --git a/examples/02-hub-spoke/outputs.tf b/src/outputs.tf similarity index 87% rename from examples/02-hub-spoke/outputs.tf rename to src/outputs.tf index 09f30ee..55aee09 100644 --- a/examples/02-hub-spoke/outputs.tf +++ b/src/outputs.tf @@ -19,17 +19,17 @@ output "management_project_id" { output "connectivity_network_area_id" { description = "The network area ID created by the regional module." - value = module.connectivity.network_area_id + value = try(module.connectivity[0].network_area_id, null) } output "connectivity_project_id" { description = "The project ID of the connectivity project." - value = module.connectivity.project_id + value = try(module.connectivity[0].project_id, null) } output "connectivity_firewall_public_ip" { description = "The public IP of the firewall." - value = module.connectivity.firewall_public_ip + value = try(module.connectivity[0].firewall_public_ip, null) } output "sandbox_projects" { diff --git a/examples/01-standalone/providers.tf b/src/providers.tf similarity index 100% rename from examples/01-standalone/providers.tf rename to src/providers.tf diff --git a/examples/01-standalone/terraform.tf b/src/terraform.tf similarity index 100% rename from examples/01-standalone/terraform.tf rename to src/terraform.tf diff --git a/src/tests/hub_spoke.tftest.hcl b/src/tests/hub_spoke.tftest.hcl new file mode 100644 index 0000000..25df454 --- /dev/null +++ b/src/tests/hub_spoke.tftest.hcl @@ -0,0 +1,145 @@ +variables { + owner_email = "matthias.hauber@prodyna.com" + company_name = "Test Corp" + company_code = "tst" + organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + region = "eu01" + + labels = { + managed_by = "opentofu" + environment = "test" + } + + rm_folders = { + platform = { + name = "Platform - TST" + owner_emails = [] + reader_emails = [] + } + landing_zones_corporate = { + name = "Landing Zones - Corporate - TST" + owner_emails = [] + reader_emails = [] + } + landing_zones_public = { + name = "Landing Zones - Public - TST" + owner_emails = [] + reader_emails = [] + } + sandboxes = { + name = "Sandboxes - TST" + owner_emails = [] + reader_emails = [] + } + } + + devops = { + git_flavor = "git-10" + allowed_network_ranges = ["0.0.0.0/0"] + } + + connectivity = { + dns_zones = { + "test-corp" = { + dns_name = "test-corp.stackit.run" + } + } + network_area = { + ranges = ["10.0.0.0/16"] + transfer_network = "10.255.0.0/24" + min_prefix_length = 24 + max_prefix_length = 28 + default_prefix_length = 25 + } + } + + sandboxes = [] + + landing_zones = { + "test-corporate" = { + project_name = "Test Corporate LZ" + project_code = "tcorp" + owner_email = "matthias.hauber@prodyna.com" + env = "test" + corporate = true + network_prefix_length = 25 + } + "test-public" = { + project_name = "Test Public LZ" + project_code = "tpub" + owner_email = "matthias.hauber@prodyna.com" + env = "test" + corporate = false + } + } +} + +# Validates hub-spoke without firewall: connectivity module is created, no firewall. +# Resource-computed outputs (network_area_id, project_id) are unknown during plan +# and cannot be asserted โ€” a successful plan is the primary validation. +run "hub_spoke_plan" { + command = plan + + assert { + condition = output.connectivity_firewall_public_ip == null + error_message = "Firewall public IP must be null when no firewall is configured." + } + + assert { + condition = length(output.landing_zone_projects) == 2 + error_message = "Expected 2 landing zones to be created." + } + + assert { + condition = output.landing_zone_projects["test-corporate"].landing_zone_type == "corporate" + error_message = "test-corporate must be a corporate landing zone." + } + + assert { + condition = output.landing_zone_projects["test-public"].landing_zone_type == "public" + error_message = "test-public must be a public landing zone." + } +} + +# Validates hub-spoke with firewall override. +run "hub_spoke_with_firewall_plan" { + command = plan + + variables { + connectivity = { + dns_zones = { + "test-corp" = { + dns_name = "test-corp.stackit.run" + } + } + network_area = { + ranges = ["10.0.0.0/16"] + transfer_network = "10.255.0.0/24" + min_prefix_length = 24 + max_prefix_length = 28 + default_prefix_length = 25 + } + firewall = { + zone = "eu01-m" + flavor = "c1.2" + lan_network_range = "10.0.0.0/28" + wan_network_range = "10.0.0.16/28" + } + } + } + + assert { + condition = length(output.landing_zone_projects) == 2 + error_message = "Expected 2 landing zones to be created." + } + + assert { + condition = output.landing_zone_projects["test-corporate"].landing_zone_type == "corporate" + error_message = "test-corporate must be a corporate landing zone." + } + + assert { + condition = output.landing_zone_projects["test-public"].landing_zone_type == "public" + error_message = "test-public must be a public landing zone." + } +} diff --git a/src/tests/hub_spoke_firewall.tftest.hcl b/src/tests/hub_spoke_firewall.tftest.hcl new file mode 100644 index 0000000..699f5ed --- /dev/null +++ b/src/tests/hub_spoke_firewall.tftest.hcl @@ -0,0 +1,108 @@ +variables { + owner_email = "matthias.hauber@prodyna.com" + company_name = "Test Corp" + company_code = "tst" + organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + region = "eu01" + + labels = { + managed_by = "opentofu" + environment = "test" + } + + rm_folders = { + platform = { + name = "Platform - TST" + owner_emails = [] + reader_emails = [] + } + landing_zones_corporate = { + name = "Landing Zones - Corporate - TST" + owner_emails = [] + reader_emails = [] + } + landing_zones_public = { + name = "Landing Zones - Public - TST" + owner_emails = [] + reader_emails = [] + } + sandboxes = { + name = "Sandboxes - TST" + owner_emails = [] + reader_emails = [] + } + } + + devops = { + git_flavor = "git-10" + allowed_network_ranges = ["0.0.0.0/0"] + } + + connectivity = { + dns_zones = { + "test-corp" = { + dns_name = "test-corp.stackit.run" + } + } + network_area = { + ranges = ["10.0.0.0/16"] + transfer_network = "10.255.0.0/24" + min_prefix_length = 24 + max_prefix_length = 28 + default_prefix_length = 25 + } + firewall = { + zone = "eu01-m" + flavor = "c1.2" + lan_network_range = "10.0.0.0/28" + wan_network_range = "10.0.0.16/28" + } + } + + sandboxes = [ + { + project_name = "Test Sandbox" + project_owner_email = "matthias.hauber@prodyna.com" + } + ] + + landing_zones = { + "test-corporate" = { + project_name = "Test Corporate LZ" + project_code = "tcorp" + owner_email = "matthias.hauber@prodyna.com" + env = "test" + corporate = true + network_prefix_length = 25 + } + "test-public" = { + project_name = "Test Public LZ" + project_code = "tpub" + owner_email = "matthias.hauber@prodyna.com" + env = "test" + corporate = false + } + } +} + +# Validates hub-spoke-firewall variant. Resource-computed outputs (network_area_id, +# project_id, firewall_public_ip) are unknown during plan and cannot be asserted โ€” +# a successful plan is the primary validation. +run "hub_spoke_firewall_plan" { + command = plan + + assert { + condition = length(output.landing_zone_projects) == 2 + error_message = "Expected 2 landing zones to be created." + } + + assert { + condition = output.landing_zone_projects["test-corporate"].landing_zone_type == "corporate" + error_message = "test-corporate must be a corporate landing zone." + } + + assert { + condition = output.landing_zone_projects["test-public"].landing_zone_type == "public" + error_message = "test-public must be a public landing zone." + } +} diff --git a/src/tests/standalone.tftest.hcl b/src/tests/standalone.tftest.hcl new file mode 100644 index 0000000..60c2081 --- /dev/null +++ b/src/tests/standalone.tftest.hcl @@ -0,0 +1,89 @@ +variables { + owner_email = "matthias.hauber@prodyna.com" + company_name = "Test Corp" + company_code = "tst" + organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + region = "eu01" + + labels = { + managed_by = "opentofu" + environment = "test" + } + + rm_folders = { + platform = { + name = "Platform - TST" + owner_emails = [] + reader_emails = [] + } + landing_zones_corporate = { + name = "Landing Zones - Corporate - TST" + owner_emails = [] + reader_emails = [] + } + landing_zones_public = { + name = "Landing Zones - Public - TST" + owner_emails = [] + reader_emails = [] + } + sandboxes = { + name = "Sandboxes - TST" + owner_emails = [] + reader_emails = [] + } + } + + devops = { + git_flavor = "git-10" + allowed_network_ranges = ["0.0.0.0/0"] + } + + # No connectivity โ€” standalone flavour has no network area or firewall + connectivity = null + + sandboxes = [ + { + project_name = "Test Sandbox" + project_owner_email = "matthias.hauber@prodyna.com" + } + ] + + landing_zones = { + "test-public" = { + project_name = "Test Public LZ" + project_code = "tpub" + owner_email = "matthias.hauber@prodyna.com" + env = "test" + corporate = false + } + } +} + +run "standalone_plan" { + command = plan + + assert { + condition = output.connectivity_network_area_id == null + error_message = "Network area must be null in standalone configuration." + } + + assert { + condition = output.connectivity_project_id == null + error_message = "Connectivity project must not be created in standalone configuration." + } + + assert { + condition = output.connectivity_firewall_public_ip == null + error_message = "Firewall public IP must be null in standalone configuration." + } + + assert { + condition = length(output.landing_zone_projects) == 1 + error_message = "Expected 1 landing zone to be created." + } + + assert { + condition = output.landing_zone_projects["test-public"].landing_zone_type == "public" + error_message = "test-public must be a public landing zone." + } +} diff --git a/src/variables.tf b/src/variables.tf new file mode 100644 index 0000000..fc2f0f0 --- /dev/null +++ b/src/variables.tf @@ -0,0 +1,164 @@ +############# +## GENERAL ## +############# + +variable "owner_email" { + type = string + description = "Email address of the owner. Required for STACKIT resource manager." +} + +variable "company_name" { + type = string + description = "Name of the company." +} + +variable "company_code" { + type = string + description = "Company code used in resource naming conventions." +} + +variable "organization_id" { + type = string + description = "Container ID of the root organization." +} + +variable "region" { + type = string + description = "STACKIT region for regional resources." + default = "eu01" +} + +variable "labels" { + type = map(string) + description = "Additional labels to apply to all resources." + default = {} +} + +variable "organization_owners" { + type = list(string) + description = "List of organization owners." + default = [] +} + +variable "organization_auditors" { + type = list(string) + description = "List of organization auditors." + default = [] +} + +variable "devops" { + type = object({ + git_flavor = optional(string, null) + allowed_network_ranges = optional(list(string), ["0.0.0.0/0"]) + }) + description = "DevOps module configuration. Set to null to skip deployment." + default = null +} + +variable "rm_folders" { + type = map(object({ + name = string + description = optional(string, null) + owner_emails = list(string) + reader_emails = list(string) + })) + description = "Map of resource manager folders to create under the root organization." + default = { + platform = { + name = "Platform" + owner_emails = [] + reader_emails = [] + } + landing_zones_corporate = { + name = "Landing Zones - Corporate" + owner_emails = [] + reader_emails = [] + } + landing_zones_public = { + name = "Landing Zones - Public" + owner_emails = [] + reader_emails = [] + } + sandboxes = { + name = "Sandboxes" + owner_emails = [] + reader_emails = [] + } + } +} + +################## +## CONNECTIVITY ## +################## + +variable "connectivity" { + type = object({ + dns_zones = optional(map(object({ + dns_name = string + name = optional(string, null) + contact_email = optional(string, null) + type = optional(string, "primary") + acl = optional(string, null) + description = optional(string, null) + default_ttl = optional(number, 3600) + })), {}) + network_area = optional(object({ + ranges = list(string) + transfer_network = string + min_prefix_length = optional(number, 24) + max_prefix_length = optional(number, 28) + default_prefix_length = optional(number, 28) + }), null) + firewall = optional(object({ + zone = string + flavor = string + lan_network_range = string + wan_network_range = string + lan_ip = optional(string, null) + wan_ip = optional(string, null) + }), null) + }) + description = "Connectivity configuration including DNS zones, network area, and pfSense firewall. Set firewall/network_area to null to skip deployment." + default = null +} + +############### +## SANDBOXES ## +############### + +variable "sandboxes" { + type = list(object({ + project_name = string + owner_emails = optional(list(string)) + project_owner_email = string + })) + description = "List of sandboxes to create." + default = [] +} + +################## +## LANDING ZONE ## +################## + +variable "landing_zones" { + type = map(object({ + project_name = string + project_code = string + owner_email = string + # Set to true for corporate landing zones (connected to network area), false for public + corporate = optional(bool, true) + env = optional(string, "dev") + role_assignments = optional(list(object({ + role = string + subject = string + })), []) + network_prefix_length = optional(number, null) + custom_roles = optional(list(object({ + name = string + description = string + permissions = list(string) + })), []) + })) + description = "Map of landing zones to create. Set corporate = true for network area connectivity, false for public." + default = {} +} From f869a35f5c04871fce0bd41cb7e16288757fc2b4 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Mon, 4 May 2026 17:47:00 +0200 Subject: [PATCH 06/30] feat: update README with new requirements and inputs for connectivity configuration --- src/README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/README.md b/src/README.md index 38fedfb..affd5bd 100644 --- a/src/README.md +++ b/src/README.md @@ -4,7 +4,9 @@ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | 0.88.0 | +| [stackit](#requirement\_stackit) | 0.93.0 | +| [time](#requirement\_time) | 0.13.1 | +| [vault](#requirement\_vault) | 5.7.0 | ## Providers @@ -14,11 +16,12 @@ No providers. | Name | Source | Version | |------|--------|---------| -| [devops](#module\_devops) | ../../modules/devops | n/a | -| [governance](#module\_governance) | ../../modules/governance | n/a | -| [landing\_zone](#module\_landing\_zone) | ../../modules/landing-zone | n/a | -| [management](#module\_management) | ../../modules/management | n/a | -| [sandboxes](#module\_sandboxes) | ../../modules/sandboxes | n/a | +| [connectivity](#module\_connectivity) | ./modules/connectivity | n/a | +| [devops](#module\_devops) | ./modules/devops | n/a | +| [governance](#module\_governance) | ./modules/governance | n/a | +| [landing\_zone](#module\_landing\_zone) | ./modules/landing-zone | n/a | +| [management](#module\_management) | ./modules/management | n/a | +| [sandboxes](#module\_sandboxes) | ./modules/sandboxes | n/a | ## Resources @@ -30,19 +33,25 @@ No resources. |------|-------------|------|---------|:--------:| | [company\_code](#input\_company\_code) | Company code used in resource naming conventions. | `string` | n/a | yes | | [company\_name](#input\_company\_name) | Name of the company. | `string` | n/a | yes | +| [connectivity](#input\_connectivity) | Connectivity configuration including DNS zones, network area, and pfSense firewall. Set firewall/network\_area to null to skip deployment. |
object({
dns_zones = optional(map(object({
dns_name = string
name = optional(string, null)
contact_email = optional(string, null)
type = optional(string, "primary")
acl = optional(string, null)
description = optional(string, null)
default_ttl = optional(number, 3600)
})), {})
network_area = optional(object({
ranges = list(string)
transfer_network = string
min_prefix_length = optional(number, 24)
max_prefix_length = optional(number, 28)
default_prefix_length = optional(number, 28)
}), null)
firewall = optional(object({
zone = string
flavor = string
lan_network_range = string
wan_network_range = string
lan_ip = optional(string, null)
wan_ip = optional(string, null)
}), null)
})
| `null` | no | +| [devops](#input\_devops) | DevOps module configuration. Set to null to skip deployment. |
object({
git_flavor = optional(string, null)
allowed_network_ranges = optional(list(string), ["0.0.0.0/0"])
})
| `null` | no | | [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no | -| [landing\_zones](#input\_landing\_zones) | Map of landing zones to create (public, without network area). |
map(object({
project_name = string
project_code = string
owner_email = string
env = optional(string, "dev")
role_assignments = optional(list(object({
role = string
subject = string
})), [])
network_prefix_length = optional(number, null)
custom_roles = optional(list(object({
name = string
description = string
permissions = list(string)
})), [])
}))
| `{}` | no | +| [landing\_zones](#input\_landing\_zones) | Map of landing zones to create. Set corporate = true for network area connectivity, false for public. |
map(object({
project_name = string
project_code = string
owner_email = string
# Set to true for corporate landing zones (connected to network area), false for public
corporate = optional(bool, true)
env = optional(string, "dev")
role_assignments = optional(list(object({
role = string
subject = string
})), [])
network_prefix_length = optional(number, null)
custom_roles = optional(list(object({
name = string
description = string
permissions = list(string)
})), [])
}))
| `{}` | no | | [organization\_auditors](#input\_organization\_auditors) | List of organization auditors. | `list(string)` | `[]` | no | | [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes | | [organization\_owners](#input\_organization\_owners) | List of organization owners. | `list(string)` | `[]` | no | | [owner\_email](#input\_owner\_email) | Email address of the owner. Required for STACKIT resource manager. | `string` | n/a | yes | | [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no | +| [rm\_folders](#input\_rm\_folders) | Map of resource manager folders to create under the root organization. |
map(object({
name = string
description = optional(string, null)
owner_emails = list(string)
reader_emails = list(string)
}))
|
{
"landing_zones_corporate": {
"name": "Landing Zones - Corporate",
"owner_emails": [],
"reader_emails": []
},
"landing_zones_public": {
"name": "Landing Zones - Public",
"owner_emails": [],
"reader_emails": []
},
"platform": {
"name": "Platform",
"owner_emails": [],
"reader_emails": []
},
"sandboxes": {
"name": "Sandboxes",
"owner_emails": [],
"reader_emails": []
}
}
| no | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | ## Outputs | Name | Description | |------|-------------| +| [connectivity\_firewall\_public\_ip](#output\_connectivity\_firewall\_public\_ip) | The public IP of the firewall. | +| [connectivity\_network\_area\_id](#output\_connectivity\_network\_area\_id) | The network area ID created by the regional module. | +| [connectivity\_project\_id](#output\_connectivity\_project\_id) | The project ID of the connectivity project. | | [devops\_project\_id](#output\_devops\_project\_id) | The project ID of the DevOps project. | | [governance\_folder\_ids](#output\_governance\_folder\_ids) | Map of governance folder names to their container IDs. | | [landing\_zone\_projects](#output\_landing\_zone\_projects) | Map of landing zone project IDs. | From 2712f9271235a949607c8e4314e15a4e37caadc2 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Mon, 4 May 2026 17:48:47 +0200 Subject: [PATCH 07/30] feat: update README to reflect changes in Terraform version and enhance deployment flavours section --- README.md | 77 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 3e832e1..eb83514 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Accelerate your STACKIT cloud adoption with production-ready, modular landing zones.** -[![Terraform](https://img.shields.io/badge/Terraform-1.14+-623CE4?logo=terraform&logoColor=white)](https://www.terraform.io/) +[![Terraform](https://img.shields.io/badge/Terraform-1.10+-623CE4?logo=terraform&logoColor=white)](https://www.terraform.io/) [![OpenTofu](https://img.shields.io/badge/OpenTofu-1.11+-FFDA18?logo=opentofu&logoColor=black)](https://opentofu.org/) [![STACKIT](https://img.shields.io/badge/STACKIT-Cloud-00A9E0)](https://www.stackit.de/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) @@ -17,7 +17,7 @@ The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based fr - **๐Ÿ—๏ธ Modular Architecture** โ€” Compose your infrastructure using reusable, tested Terraform modules - **๐Ÿ” Security First** โ€” Pre-configured RBAC, secrets management, and network segmentation -- **๐Ÿ“ Scalable Templates** โ€” Start small and grow with ready-to-use deployment templates +- **๐Ÿ“ Three Deployment Flavours** โ€” Start standalone and evolve to hub-spoke with or without a firewall - **๐ŸŒ Multi-Environment** โ€” Seamlessly manage production and non-production workloads - **โšก Quick Start** โ€” Get up and running in minutes with sensible defaults @@ -25,11 +25,10 @@ The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based fr | Module | Description | |--------|-------------| -| `connectivity-global` | Network areas and regional IP range management including transfer networks and nameservers | -| `connectivity-regional` | Regional connectivity project with WAN/LAN networks, pfSense firewall appliance, public IP, and network area routing | +| `connectivity` | Connectivity hub project with network area, WAN/LAN networks, optional pfSense firewall, and DNS zones | | `devops` | DevOps project with RBAC and managed Git instance | -| `governance` | Resource Manager folder hierarchy, custom roles, and organization and folder-level role assignments | -| `landing-zone` | Landing zone project with RBAC, networking, Secrets Manager, Object Storage, service accounts, and SKE Kubernetes cluster | +| `governance` | Resource Manager folder hierarchy, custom roles, and organization-level role assignments | +| `landing-zone` | Landing zone project with RBAC, networking, Secrets Manager, Object Storage, and service accounts | | `management` | Management project with Secrets Manager, Object Storage, service accounts, and observability | | `sandboxes` | Sandbox projects with RBAC role assignments for experimentation workloads | @@ -38,20 +37,53 @@ The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based fr ```bash # Clone the repository git clone https://github.com/stackitcloud/stackit-landing-zone.git +cd stackit-landing-zone/src -# Navigate to a template -cd examples/01-small-scale +# Copy and edit the tfvars for your desired flavour (see Deployment Flavours below) +cp config/standalone.tfvars terraform.tfvars -# Fill out the values - -# Initialize and apply -terraform init -terraform plan -terraform apply +# Initialize and deploy +tofu init +tofu plan -var-file=terraform.tfvars +tofu apply -var-file=terraform.tfvars ``` ๐Ÿ“– See the [Getting Started Guide](docs/getting-started.md) for detailed instructions. +## ๐Ÿ—‚๏ธ Deployment Flavours + +Three ready-to-use configurations are provided in `src/config/`: + +| Flavour | Config file | Description | +|---------|-------------|-------------| +| **Standalone** | `standalone.tfvars` | Governance, management, devops, and public landing zones only. No network area or firewall. | +| **Hub-Spoke** | `hub-and-spoke.tfvars` | Adds a connectivity hub with a network area and DNS zones. Corporate landing zones connect via the network area. | +| **Hub-Spoke + Firewall** | `hub-and-spoke-firewall.tfvars` | Full hub-spoke topology with a pfSense firewall appliance on the WAN/LAN boundary. | + +### Key Variables + +| Variable | Type | Description | +|----------|------|-------------| +| `connectivity` | `object` / `null` | Set to `null` to skip the connectivity module entirely (standalone). Include `network_area` and optionally `firewall` for hub-spoke variants. | +| `devops` | `object` / `null` | Set to `null` to skip DevOps deployment. Provide `git_flavor` and `allowed_network_ranges` to enable. | +| `landing_zones` | `map(object)` | Map of landing zones. Set `corporate = true` to attach to the network area, `false` for public. | +| `sandboxes` | `list(object)` | List of sandbox projects for experimentation. | + +## ๐Ÿงช Testing + +Tests for all three flavours are located in `src/tests/` and use the native OpenTofu test framework: + +```bash +cd src +tofu test +``` + +| Test file | Flavour tested | +|-----------|---------------| +| `standalone.tftest.hcl` | Standalone โ€” no connectivity module | +| `hub_spoke.tftest.hcl` | Hub-spoke without and with firewall (two runs) | +| `hub_spoke_firewall.tftest.hcl` | Hub-spoke with firewall | + ## ๐Ÿ“š Documentation - [Getting Started](docs/getting-started.md) @@ -59,11 +91,6 @@ terraform apply ## ๐Ÿ” Linting (TFLint) -The repository includes automated Terraform linting with `tflint` in GitHub Actions. - -- Workflow: `.github/workflows/tflint.yml` -- Config: `.tflint.hcl` - Run locally: ```bash @@ -71,22 +98,14 @@ tflint --init tflint --recursive ``` -Additionally, Terraform variable validations enforce flavor naming patterns for: - -- `modules/connectivity-regional` (`firewall_flavor`) -- `modules/devops` (`git_flavor`) -- `modules/landing-zone` (`kubernetes_clusters[*].node_pools[*].machine_type`) +Variable validations enforce flavor naming patterns for `firewall.flavor` (connectivity module) and `git_flavor` (devops module). Use `stackit server machine-type list` and the STACKIT Git API docs to verify currently available flavors. -Use `stackit server machine-type list` and the STACKIT Git API docs to verify currently available flavors. - -For live validation against current STACKIT SKUs, CI also runs: +For live validation against current STACKIT SKUs: ```bash python3 scripts/validate_stackit_flavors.py ``` -By default it uses `https://pim.api.stackit.cloud/v2/skus` and fails if a configured flavor is not currently available. - ## ๐Ÿค Contributing Contributions are welcome! Please feel free to submit a Pull Request. From 760700da89ca1c1656e767fa5dd9a80dd7a15f0e Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Mon, 4 May 2026 17:50:29 +0200 Subject: [PATCH 08/30] feat: update README to reflect changes in Terraform variable file naming and deployment commands --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eb83514..ca1c6e0 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ git clone https://github.com/stackitcloud/stackit-landing-zone.git cd stackit-landing-zone/src # Copy and edit the tfvars for your desired flavour (see Deployment Flavours below) -cp config/standalone.tfvars terraform.tfvars +cp config/standalone.tfvars terraform.auto.tfvars # Initialize and deploy tofu init -tofu plan -var-file=terraform.tfvars -tofu apply -var-file=terraform.tfvars +tofu plan +tofu apply ``` ๐Ÿ“– See the [Getting Started Guide](docs/getting-started.md) for detailed instructions. From fd4b4384c349ef42ff5f3110645a6496d6d17387 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Mon, 4 May 2026 17:52:12 +0200 Subject: [PATCH 09/30] feat: remove key variables section from README for clarity --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index ca1c6e0..c509bd2 100644 --- a/README.md +++ b/README.md @@ -60,15 +60,6 @@ Three ready-to-use configurations are provided in `src/config/`: | **Hub-Spoke** | `hub-and-spoke.tfvars` | Adds a connectivity hub with a network area and DNS zones. Corporate landing zones connect via the network area. | | **Hub-Spoke + Firewall** | `hub-and-spoke-firewall.tfvars` | Full hub-spoke topology with a pfSense firewall appliance on the WAN/LAN boundary. | -### Key Variables - -| Variable | Type | Description | -|----------|------|-------------| -| `connectivity` | `object` / `null` | Set to `null` to skip the connectivity module entirely (standalone). Include `network_area` and optionally `firewall` for hub-spoke variants. | -| `devops` | `object` / `null` | Set to `null` to skip DevOps deployment. Provide `git_flavor` and `allowed_network_ranges` to enable. | -| `landing_zones` | `map(object)` | Map of landing zones. Set `corporate = true` to attach to the network area, `false` for public. | -| `sandboxes` | `list(object)` | List of sandbox projects for experimentation. | - ## ๐Ÿงช Testing Tests for all three flavours are located in `src/tests/` and use the native OpenTofu test framework: From 74f8c95556d767648f945d36c121e8e9f236fa2c Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 13:55:11 +0200 Subject: [PATCH 10/30] feat: enhance firewall configuration and add DNS zones support Co-authored-by: Copilot --- src/config/hub-and-spoke-firewall.tfvars | 13 ++- src/config/hub-and-spoke.tfvars | 8 +- src/config/standalone.tfvars | 8 +- src/modules/connectivity/2-project.tf | 17 --- src/modules/connectivity/5-firewall.tf | 40 +++---- src/modules/connectivity/6-dns-zones.tf | 16 +++ src/modules/connectivity/README.md | 107 ------------------ src/modules/connectivity/outputs.tf | 4 +- src/modules/connectivity/variables.tf | 17 +-- src/modules/landing-zone/4-secrets-manager.tf | 10 +- src/modules/landing-zone/5-bucket.tf | 15 +-- src/modules/landing-zone/6-service-account.tf | 10 +- src/outputs.tf | 8 +- src/tests/hub_spoke.tftest.hcl | 45 +------- src/tests/hub_spoke_firewall.tftest.hcl | 1 + src/variables.tf | 19 ++-- 16 files changed, 77 insertions(+), 261 deletions(-) create mode 100644 src/modules/connectivity/6-dns-zones.tf delete mode 100644 src/modules/connectivity/README.md diff --git a/src/config/hub-and-spoke-firewall.tfvars b/src/config/hub-and-spoke-firewall.tfvars index 60c47c2..94cadad 100644 --- a/src/config/hub-and-spoke-firewall.tfvars +++ b/src/config/hub-and-spoke-firewall.tfvars @@ -54,8 +54,9 @@ connectivity = { # Delete the variable to skip firewall deployment (network area and routing still created) firewall = { - zone = "eu01-m" - flavor = "c1.2" + zone = "eu01-m" + flavor = "c1.2" + image_name = "pfsense-2.7.2" lan_network_range = "10.0.0.0/28" wan_network_range = "10.0.0.16/28" } @@ -65,10 +66,10 @@ connectivity = { ## DEVOPS ## ############ -devops = { - git_flavor = "git-10" - allowed_network_ranges = ["0.0.0.0/0"] -} +# devops = { +# git_flavor = "git-10" +# allowed_network_ranges = ["0.0.0.0/0"] +# } ############### ## SANDBOXES ## diff --git a/src/config/hub-and-spoke.tfvars b/src/config/hub-and-spoke.tfvars index 12dce87..cf27e8e 100644 --- a/src/config/hub-and-spoke.tfvars +++ b/src/config/hub-and-spoke.tfvars @@ -57,10 +57,10 @@ connectivity = { ## DEVOPS ## ############ -devops = { - git_flavor = "git-10" - allowed_network_ranges = ["0.0.0.0/0"] -} +# devops = { +# git_flavor = "git-10" +# allowed_network_ranges = ["0.0.0.0/0"] +# } ############### ## SANDBOXES ## diff --git a/src/config/standalone.tfvars b/src/config/standalone.tfvars index d0d9a4d..71c99a1 100644 --- a/src/config/standalone.tfvars +++ b/src/config/standalone.tfvars @@ -35,10 +35,10 @@ labels = { ## DEVOPS ## ############ -devops = { - git_flavor = "git-10" - allowed_network_ranges = ["0.0.0.0/0"] -} +# devops = { +# git_flavor = "git-10" +# allowed_network_ranges = ["0.0.0.0/0"] +# } ############### ## SANDBOXES ## diff --git a/src/modules/connectivity/2-project.tf b/src/modules/connectivity/2-project.tf index 577ad4e..c656edd 100644 --- a/src/modules/connectivity/2-project.tf +++ b/src/modules/connectivity/2-project.tf @@ -24,20 +24,3 @@ resource "stackit_authorization_project_role_assignment" "this" { role = each.value.role subject = each.value.subject } - -############### -## DNS ZONES ## -############### - -resource "stackit_dns_zone" "this" { - for_each = var.dns_zones - - project_id = stackit_resourcemanager_project.this.project_id - name = each.value.name != null ? each.value.name : each.value.dns_name - dns_name = each.value.dns_name - contact_email = each.value.contact_email - type = each.value.type - acl = each.value.acl - description = each.value.description - default_ttl = each.value.default_ttl -} diff --git a/src/modules/connectivity/5-firewall.tf b/src/modules/connectivity/5-firewall.tf index ff0c44a..b30957e 100644 --- a/src/modules/connectivity/5-firewall.tf +++ b/src/modules/connectivity/5-firewall.tf @@ -1,47 +1,35 @@ -##################### -## PFSENSE - IMAGE ## -##################### +########### +## IMAGE ## +########### -# resource "terraform_data" "pfsense_image_file" { -# triggers_replace = [ -# timestamp() -# ] - -# provisioner "local-exec" { -# command = "curl -o pfsense.qcow2 https://pfsense.object.storage.eu01.onstackit.cloud/pfsense-ce-2.7.2-amd64-10-12-2024.qcow2" -# } -# } - -resource "stackit_image" "pfsense_image" { +resource "stackit_image" "firewall" { count = var.firewall != null ? 1 : 0 project_id = stackit_resourcemanager_project.this.project_id - name = "pfsense-2.7.2-amd64-image" - local_file_path = "./pfsense.qcow2" + name = var.firewall.image_name + local_file_path = "./firewall-image.qcow2" disk_format = "qcow2" min_disk_size = 10 min_ram = 2 config = { uefi = false } - - # depends_on = [terraform_data.pfsense_image_file] } ############ ## VOLUME ## ############ -resource "stackit_volume" "pfsense_vol" { +resource "stackit_volume" "firewall" { count = var.firewall != null ? 1 : 0 project_id = stackit_resourcemanager_project.this.project_id - name = "pfsense-2.7.2-root" + name = var.firewall.image_name availability_zone = var.firewall.zone - size = 16 - performance_class = "storage_premium_perf4" + size = var.firewall.volume_size + performance_class = var.firewall.volume_performance_class source = { - id = stackit_image.pfsense_image[0].image_id + id = stackit_image.firewall[0].image_id type = "image" } } @@ -51,14 +39,14 @@ resource "stackit_volume" "pfsense_vol" { ############ # after rollout: https://docs.stackit.cloud/products/quick-deployments/pfsense-firewall/tutorials/configure-pfsense/ -resource "stackit_server" "pfsense_Server" { +resource "stackit_server" "firewall" { count = var.firewall != null ? 1 : 0 project_id = stackit_resourcemanager_project.this.project_id - name = "pfSense" + name = var.firewall.image_name boot_volume = { source_type = "volume" - source_id = stackit_volume.pfsense_vol[0].volume_id + source_id = stackit_volume.firewall[0].volume_id } availability_zone = var.firewall.zone machine_type = var.firewall.flavor diff --git a/src/modules/connectivity/6-dns-zones.tf b/src/modules/connectivity/6-dns-zones.tf new file mode 100644 index 0000000..4d3a0ca --- /dev/null +++ b/src/modules/connectivity/6-dns-zones.tf @@ -0,0 +1,16 @@ +############### +## DNS ZONES ## +############### + +resource "stackit_dns_zone" "this" { + for_each = var.dns_zones + + project_id = stackit_resourcemanager_project.this.project_id + name = each.value.name != null ? each.value.name : each.value.dns_name + dns_name = each.value.dns_name + contact_email = each.value.contact_email + type = each.value.type + acl = each.value.acl + description = each.value.description + default_ttl = each.value.default_ttl +} \ No newline at end of file diff --git a/src/modules/connectivity/README.md b/src/modules/connectivity/README.md deleted file mode 100644 index df995ba..0000000 --- a/src/modules/connectivity/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# STACKIT pfSense Deployment - -Terraform script to deploy an pfSense firewall into STACKIT Cloud. - -Deployment overview: -![](deployment.svg) - -The Terraform deployment consists of: -+ WAN Network -+ LAN Network -+ pfSense firewall VM + disk volume -+ FloatingIP for firewall VM -+ deactivating port security on firewall ports - -## Setup -**Requirements:** -+ Terraform installed -+ Access to a STACKIT project -+ STACKIT Service Account Key - -### Installation -1. Clone Repo -1. Set Project ID in `01-config.tf` -1. Create & Save a STACKIT Service Account Token and place it in the `secrets.json` file. -1. Run Terraform `terraform apply` - -## Default Configuration - -### Interfaces -1. `vtnet0` WAN -1. `vtnet1` LAN - -### NAT -Masqurade (Outbound NAT) Traffic from `LAN` to `WAN` - -### Dashboard -Customized Widgets and CSS settings - -### Password -Set default password for admin to STACKIT123! - -### Interface Access -Disabled Referer-Check -Enable allow all wan adresses to connect to the WebUI - -Now you can enter the WebUI via the FloatingIP on port 443 the default login is admin:STACKIT123! - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.88.0 | - -### Providers - -| Name | Version | -|------|---------| -| [stackit](#provider\_stackit) | 0.88.0 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [stackit_authorization_project_role_assignment.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_project_role_assignment) | resource | -| [stackit_image.pfsense](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/image) | resource | -| [stackit_network.lan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network) | resource | -| [stackit_network.wan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network) | resource | -| [stackit_network_area_route.default](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_area_route) | resource | -| [stackit_network_interface.lan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_interface) | resource | -| [stackit_network_interface.wan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_interface) | resource | -| [stackit_public_ip.wan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/public_ip) | resource | -| [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | -| [stackit_server.pfsense](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/server) | resource | -| [stackit_volume.pfsense](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/volume) | resource | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | -| [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if network is enabled. | `string` | n/a | yes | -| [organization\_id](#input\_organization\_id) | Organization ID, required for network area route configuration. | `string` | n/a | yes | -| [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | -| [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | -| [firewall\_flavor](#input\_firewall\_flavor) | Firewall VM Flavor | `string` | `"c1.2"` | no | -| [firewall\_ip](#input\_firewall\_ip) | IP address of the firewall | `string` | `"10.0.0.220"` | no | -| [firewall\_zone](#input\_firewall\_zone) | STACKIT Availability Zone | `string` | `"eu01-m"` | no | -| [labels](#input\_labels) | Additional labels to apply to all folders. | `map(string)` | `{}` | no | -| [project\_name](#input\_project\_name) | Name of the STACKIT project to create. | `string` | `null` | no | -| [role\_assignments](#input\_role\_assignments) | List of role assignments for the project. Subject can be a user email or service account email. |
list(object({
role = string
subject = string
}))
| `[]` | no | -| [vnet\_range](#input\_vnet\_range) | CIDR range for the project VNet. Required if network is enabled. | `string` | `"10.0.0.0/24"` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [pfsense\_public\_ip](#output\_pfsense\_public\_ip) | The public IP address of the pfSense firewall WAN interface. | -| [pfsense\_wan\_ip](#output\_pfsense\_wan\_ip) | The internal network area IP of the pfSense WAN interface (used as next hop in routes). | -| [project\_container\_id](#output\_project\_container\_id) | The container ID of the created STACKIT project. | -| [project\_id](#output\_project\_id) | The project ID of the created STACKIT project. | -| [project\_name](#output\_project\_name) | The name of the created STACKIT project. | - \ No newline at end of file diff --git a/src/modules/connectivity/outputs.tf b/src/modules/connectivity/outputs.tf index 0817e90..025db01 100644 --- a/src/modules/connectivity/outputs.tf +++ b/src/modules/connectivity/outputs.tf @@ -9,12 +9,12 @@ output "dns_zone_ids" { } output "firewall_next_hop_ip" { - description = "The IP address to be used as next hop for the default route in the landing zones (pfSense WAN IP)." + description = "The IP address to be used as next hop for the default route in the landing zones (firewall WAN IP)." value = var.firewall != null ? stackit_network_interface.lan[0].ipv4 : null } output "firewall_public_ip" { - description = "The public IP address of the pfSense firewall WAN interface." + description = "The public IP address of the firewall WAN interface." value = var.firewall != null ? stackit_public_ip.wan-ip[0].ip : null } diff --git a/src/modules/connectivity/variables.tf b/src/modules/connectivity/variables.tf index 21ec723..82ff9fd 100644 --- a/src/modules/connectivity/variables.tf +++ b/src/modules/connectivity/variables.tf @@ -14,14 +14,17 @@ variable "dns_zones" { variable "firewall" { type = object({ - zone = string - flavor = string - lan_network_range = string - wan_network_range = string - lan_ip = optional(string, null) - wan_ip = optional(string, null) + zone = string + flavor = string + image_name = string + volume_performance_class = optional(string, "storage_premium_perf4") + volume_size = optional(number, 16) + lan_network_range = string + wan_network_range = string + lan_ip = optional(string, null) + wan_ip = optional(string, null) }) - description = "pfSense firewall configuration. Set to null to skip firewall deployment (network area and routing are still created). lan_network_range and wan_network_range must be CIDRs within the network area range. lan_ip and wan_ip are optional; when omitted, the 5th address of the respective prefix is used (STACKIT reserves the first usable address as the gateway)." + description = "Firewall configuration. Set to null to skip firewall deployment (network area and routing are still created). lan_network_range and wan_network_range must be CIDRs within the network area range. lan_ip and wan_ip are optional; when omitted, the 5th address of the respective prefix is used (STACKIT reserves the first usable address as the gateway)." default = null validation { diff --git a/src/modules/landing-zone/4-secrets-manager.tf b/src/modules/landing-zone/4-secrets-manager.tf index b69242d..dcd0d1c 100644 --- a/src/modules/landing-zone/4-secrets-manager.tf +++ b/src/modules/landing-zone/4-secrets-manager.tf @@ -6,12 +6,4 @@ resource "stackit_secretsmanager_instance" "this" { project_id = stackit_resourcemanager_project.this.project_id name = "${var.naming_pattern}-default" # acls = length(var.secretsmanager_config.acls) > 0 ? var.secretsmanager_config.acls : null -} - -# used for vault provider to access the secrets manager instance -# resource "stackit_secretsmanager_user" "default" { -# project_id = stackit_resourcemanager_project.this.project_id -# instance_id = stackit_secretsmanager_instance.this.instance_id -# description = "Default user for accessing the Secrets Manager" -# write_enabled = true -# } \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/landing-zone/5-bucket.tf b/src/modules/landing-zone/5-bucket.tf index 5f9733e..1cc34f3 100644 --- a/src/modules/landing-zone/5-bucket.tf +++ b/src/modules/landing-zone/5-bucket.tf @@ -29,17 +29,4 @@ resource "stackit_objectstorage_credentials_group" "this" { resource "stackit_objectstorage_credential" "this" { project_id = stackit_resourcemanager_project.this.project_id credentials_group_id = stackit_objectstorage_credentials_group.this.credentials_group_id -} - -# resource "vault_kv_secret_v2" "object_storage_credentials" { -# mount = stackit_secretsmanager_instance.this.instance_id -# name = "service_account_key_${stackit_service_account.automation.name}" -# cas = 1 -# delete_all_versions = true -# data_json = jsonencode( -# { -# ACCESS_KEY = stackit_objectstorage_credential.this.access_key, -# SECRET_ACCESS_KEY = stackit_objectstorage_credential.this.secret_access_key -# } -# ) -# } \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/landing-zone/6-service-account.tf b/src/modules/landing-zone/6-service-account.tf index 07fbddb..58e2aef 100644 --- a/src/modules/landing-zone/6-service-account.tf +++ b/src/modules/landing-zone/6-service-account.tf @@ -19,12 +19,4 @@ resource "stackit_service_account_key" "automation" { rotate_when_changed = { rotation = time_rotating.key_rotate.id } -} - -# resource "vault_kv_secret_v2" "service_account_key_automation" { -# mount = stackit_secretsmanager_instance.this.instance_id -# name = "service_account_key_${stackit_service_account.automation.name}" -# cas = 1 -# delete_all_versions = true -# data_json = stackit_service_account_key.automation.json -# } \ No newline at end of file +} \ No newline at end of file diff --git a/src/outputs.tf b/src/outputs.tf index 55aee09..da4ad27 100644 --- a/src/outputs.tf +++ b/src/outputs.tf @@ -41,10 +41,10 @@ output "landing_zone_projects" { description = "Map of landing zone project IDs." value = { for k, v in module.landing_zone : k => { - project_id = v.project_id - project_name = v.project_name - dns_zone_name = v.dns_zone_dns_name - landing_zone_type = v.landing_zone_type + project_id = v.project_id + project_name = v.project_name + dns_zone_name = v.dns_zone_dns_name + landing_zone_type = v.landing_zone_type connected_network_area_id = v.connected_network_area_id == null ? "" : v.connected_network_area_id } } diff --git a/src/tests/hub_spoke.tftest.hcl b/src/tests/hub_spoke.tftest.hcl index 25df454..227857b 100644 --- a/src/tests/hub_spoke.tftest.hcl +++ b/src/tests/hub_spoke.tftest.hcl @@ -99,47 +99,4 @@ run "hub_spoke_plan" { condition = output.landing_zone_projects["test-public"].landing_zone_type == "public" error_message = "test-public must be a public landing zone." } -} - -# Validates hub-spoke with firewall override. -run "hub_spoke_with_firewall_plan" { - command = plan - - variables { - connectivity = { - dns_zones = { - "test-corp" = { - dns_name = "test-corp.stackit.run" - } - } - network_area = { - ranges = ["10.0.0.0/16"] - transfer_network = "10.255.0.0/24" - min_prefix_length = 24 - max_prefix_length = 28 - default_prefix_length = 25 - } - firewall = { - zone = "eu01-m" - flavor = "c1.2" - lan_network_range = "10.0.0.0/28" - wan_network_range = "10.0.0.16/28" - } - } - } - - assert { - condition = length(output.landing_zone_projects) == 2 - error_message = "Expected 2 landing zones to be created." - } - - assert { - condition = output.landing_zone_projects["test-corporate"].landing_zone_type == "corporate" - error_message = "test-corporate must be a corporate landing zone." - } - - assert { - condition = output.landing_zone_projects["test-public"].landing_zone_type == "public" - error_message = "test-public must be a public landing zone." - } -} +} \ No newline at end of file diff --git a/src/tests/hub_spoke_firewall.tftest.hcl b/src/tests/hub_spoke_firewall.tftest.hcl index 699f5ed..8a17f6f 100644 --- a/src/tests/hub_spoke_firewall.tftest.hcl +++ b/src/tests/hub_spoke_firewall.tftest.hcl @@ -56,6 +56,7 @@ variables { flavor = "c1.2" lan_network_range = "10.0.0.0/28" wan_network_range = "10.0.0.16/28" + image_name = "pfsense-2.7.2" } } diff --git a/src/variables.tf b/src/variables.tf index fc2f0f0..62c40e9 100644 --- a/src/variables.tf +++ b/src/variables.tf @@ -57,30 +57,30 @@ variable "devops" { variable "rm_folders" { type = map(object({ - name = string - description = optional(string, null) + name = string + description = optional(string, null) owner_emails = list(string) reader_emails = list(string) })) description = "Map of resource manager folders to create under the root organization." - default = { + default = { platform = { - name = "Platform" + name = "Platform 3" owner_emails = [] reader_emails = [] } landing_zones_corporate = { - name = "Landing Zones - Corporate" + name = "Landing Zones - Corporate 3" owner_emails = [] reader_emails = [] } landing_zones_public = { - name = "Landing Zones - Public" + name = "Landing Zones - Public 3" owner_emails = [] reader_emails = [] } sandboxes = { - name = "Sandboxes" + name = "Sandboxes 3" owner_emails = [] reader_emails = [] } @@ -112,13 +112,16 @@ variable "connectivity" { firewall = optional(object({ zone = string flavor = string + image_name = string + volume_performance_class = optional(string, "storage_premium_perf4") + volume_size = optional(number, 16) lan_network_range = string wan_network_range = string lan_ip = optional(string, null) wan_ip = optional(string, null) }), null) }) - description = "Connectivity configuration including DNS zones, network area, and pfSense firewall. Set firewall/network_area to null to skip deployment." + description = "Connectivity configuration including DNS zones, network area, and firewall. Set firewall/network_area to null to skip deployment." default = null } From d0e959f99ccbc81a7dfe047e520d0df8ab9e9f1d Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:17:40 +0200 Subject: [PATCH 11/30] feat: add observability configuration and update related resources Co-authored-by: Copilot --- src/config/hub-and-spoke-firewall.tfvars | 8 +++ src/main.tf | 1 + src/modules/landing-zone/4-secrets-manager.tf | 2 +- src/modules/landing-zone/variables.tf | 6 ++ src/modules/management/5-observability.tf | 62 ++++++++++--------- src/modules/management/variables.tf | 14 +++++ src/tests/hub_spoke_firewall.tftest.hcl | 4 ++ src/variables.tf | 14 +++++ 8 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/config/hub-and-spoke-firewall.tfvars b/src/config/hub-and-spoke-firewall.tfvars index 94cadad..5c3cc1f 100644 --- a/src/config/hub-and-spoke-firewall.tfvars +++ b/src/config/hub-and-spoke-firewall.tfvars @@ -71,6 +71,14 @@ connectivity = { # allowed_network_ranges = ["0.0.0.0/0"] # } +##################### +## MANAGEMENT ## +##################### + +observability = { + plan_name = "Observability-Starter-EU01" +} + ############### ## SANDBOXES ## ############### diff --git a/src/main.tf b/src/main.tf index cf1ee89..8469371 100644 --- a/src/main.tf +++ b/src/main.tf @@ -26,6 +26,7 @@ module "management" { parent_container_id = module.governance.folder_container_ids["platform"] organization_id = var.organization_id labels = var.labels + observability = var.observability } ################## diff --git a/src/modules/landing-zone/4-secrets-manager.tf b/src/modules/landing-zone/4-secrets-manager.tf index dcd0d1c..ea454bf 100644 --- a/src/modules/landing-zone/4-secrets-manager.tf +++ b/src/modules/landing-zone/4-secrets-manager.tf @@ -5,5 +5,5 @@ resource "stackit_secretsmanager_instance" "this" { project_id = stackit_resourcemanager_project.this.project_id name = "${var.naming_pattern}-default" - # acls = length(var.secretsmanager_config.acls) > 0 ? var.secretsmanager_config.acls : null + acls = length(var.secretsmanager_acls) > 0 ? var.secretsmanager_acls : null } \ No newline at end of file diff --git a/src/modules/landing-zone/variables.tf b/src/modules/landing-zone/variables.tf index ce78c23..2bf120c 100644 --- a/src/modules/landing-zone/variables.tf +++ b/src/modules/landing-zone/variables.tf @@ -82,4 +82,10 @@ variable "dns_zone_name" { type = string description = "Full DNS zone domain name for this landing zone. Set to null to skip DNS zone creation." default = null +} + +variable "secretsmanager_acls" { + type = list(string) + description = "List of ACL rules for the Secrets Manager instance. Set to empty list for no ACLs or null to skip Secrets Manager creation." + default = [] } \ No newline at end of file diff --git a/src/modules/management/5-observability.tf b/src/modules/management/5-observability.tf index 9ab399f..c35c8e9 100644 --- a/src/modules/management/5-observability.tf +++ b/src/modules/management/5-observability.tf @@ -2,33 +2,39 @@ ## OBSERVABILITY ## ################### -# resource "stackit_observability_instance" "this" { -# project_id = stackit_resourcemanager_project.project.project_id -# name = local.naming_pattern -# plan_name = "Observability-Starter-EU01" -# # acl = ["1.1.1.1/32", "2.2.2.2/32"] -# logs_retention_days = 30 -# traces_retention_days = 30 -# metrics_retention_days = 90 -# metrics_retention_days_5m_downsampling = 90 -# metrics_retention_days_1h_downsampling = 90 -# } +resource "stackit_observability_instance" "this" { + count = var.observability != null ? 1 : 0 -# resource "stackit_observability_credential" "this" { -# project_id = stackit_resourcemanager_project.project.project_id -# instance_id = stackit_observability_instance.this.instance_id -# description = "Default credential for accessing the Observability Instance" -# } + project_id = stackit_resourcemanager_project.this.project_id + name = var.naming_pattern + plan_name = var.observability.plan_name + acl = var.observability.acl + logs_retention_days = var.observability.logs_retention_days + traces_retention_days = var.observability.traces_retention_days + metrics_retention_days = var.observability.metrics_retention_days + metrics_retention_days_5m_downsampling = var.observability.metrics_retention_days_5m_downsampling + metrics_retention_days_1h_downsampling = var.observability.metrics_retention_days_1h_downsampling +} -# resource "vault_kv_secret_v2" "service_account_key_automation" { -# mount = stackit_secretsmanager_instance.this.instance_id -# name = "service_account_key_${stackit_service_account.automation.name}" -# cas = 1 -# delete_all_versions = true -# data_json = jsonencode( -# { -# USERNAME = "${stackit_observability_credential.this.username}", -# PASSWORD = "${stackit_observability_credential.this.password}" -# } -# ) -# } \ No newline at end of file +resource "stackit_observability_credential" "this" { + count = var.observability != null ? 1 : 0 + + project_id = stackit_resourcemanager_project.this.project_id + instance_id = stackit_observability_instance.this[0].instance_id + description = "Default credential for accessing the Observability Instance" +} + +resource "vault_kv_secret_v2" "observability" { + count = var.observability != null ? 1 : 0 + + mount = stackit_secretsmanager_instance.this.instance_id + name = "service_account_key_${stackit_service_account.automation.name}" + cas = 1 + delete_all_versions = true + data_json = jsonencode( + { + USERNAME = stackit_observability_credential.this[0].username, + PASSWORD = stackit_observability_credential.this[0].password + } + ) +} \ No newline at end of file diff --git a/src/modules/management/variables.tf b/src/modules/management/variables.tf index 99e2316..e536fd2 100644 --- a/src/modules/management/variables.tf +++ b/src/modules/management/variables.tf @@ -30,6 +30,20 @@ variable "parent_container_id" { description = "Parent container ID (folder or organization) where the project will be created." } +variable "observability" { + type = object({ + plan_name = optional(string, "Observability-Starter-EU01") + acl = optional(list(string), []) + logs_retention_days = optional(number, 30) + traces_retention_days = optional(number, 30) + metrics_retention_days = optional(number, 90) + metrics_retention_days_5m_downsampling = optional(number, 90) + metrics_retention_days_1h_downsampling = optional(number, 90) + }) + description = "Observability instance configuration. Set to null to skip observability deployment." + default = null +} + variable "role_assignments" { type = list(object({ role = string diff --git a/src/tests/hub_spoke_firewall.tftest.hcl b/src/tests/hub_spoke_firewall.tftest.hcl index 8a17f6f..4064ee6 100644 --- a/src/tests/hub_spoke_firewall.tftest.hcl +++ b/src/tests/hub_spoke_firewall.tftest.hcl @@ -38,6 +38,10 @@ variables { allowed_network_ranges = ["0.0.0.0/0"] } + observability = { + plan_name = "Observability-Starter-EU01" + } + connectivity = { dns_zones = { "test-corp" = { diff --git a/src/variables.tf b/src/variables.tf index 62c40e9..6fc1d49 100644 --- a/src/variables.tf +++ b/src/variables.tf @@ -55,6 +55,20 @@ variable "devops" { default = null } +variable "observability" { + type = object({ + plan_name = optional(string, "Observability-Starter-EU01") + acl = optional(list(string), []) + logs_retention_days = optional(number, 30) + traces_retention_days = optional(number, 30) + metrics_retention_days = optional(number, 90) + metrics_retention_days_5m_downsampling = optional(number, 90) + metrics_retention_days_1h_downsampling = optional(number, 90) + }) + description = "Observability instance configuration for the management module. Set to null to skip observability deployment." + default = null +} + variable "rm_folders" { type = map(object({ name = string From f117d9091993045308e0ea2e5a0a197f5350129f Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:30:07 +0200 Subject: [PATCH 12/30] feat: standardize firewall variable naming and update related configurations --- src/config/hub-and-spoke-firewall.tfvars | 2 +- src/modules/connectivity/5-firewall.tf | 6 +++--- src/modules/connectivity/variables.tf | 2 +- src/modules/landing-zone/variables.tf | 4 ++-- src/tests/hub_spoke_firewall.tftest.hcl | 2 +- src/variables.tf | 14 +++++++------- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/config/hub-and-spoke-firewall.tfvars b/src/config/hub-and-spoke-firewall.tfvars index 5c3cc1f..fc6356b 100644 --- a/src/config/hub-and-spoke-firewall.tfvars +++ b/src/config/hub-and-spoke-firewall.tfvars @@ -56,7 +56,7 @@ connectivity = { firewall = { zone = "eu01-m" flavor = "c1.2" - image_name = "pfsense-2.7.2" + name = "pfsense-2.7.2" lan_network_range = "10.0.0.0/28" wan_network_range = "10.0.0.16/28" } diff --git a/src/modules/connectivity/5-firewall.tf b/src/modules/connectivity/5-firewall.tf index b30957e..873467d 100644 --- a/src/modules/connectivity/5-firewall.tf +++ b/src/modules/connectivity/5-firewall.tf @@ -6,7 +6,7 @@ resource "stackit_image" "firewall" { count = var.firewall != null ? 1 : 0 project_id = stackit_resourcemanager_project.this.project_id - name = var.firewall.image_name + name = var.firewall.name local_file_path = "./firewall-image.qcow2" disk_format = "qcow2" min_disk_size = 10 @@ -24,7 +24,7 @@ resource "stackit_volume" "firewall" { count = var.firewall != null ? 1 : 0 project_id = stackit_resourcemanager_project.this.project_id - name = var.firewall.image_name + name = var.firewall.name availability_zone = var.firewall.zone size = var.firewall.volume_size performance_class = var.firewall.volume_performance_class @@ -43,7 +43,7 @@ resource "stackit_server" "firewall" { count = var.firewall != null ? 1 : 0 project_id = stackit_resourcemanager_project.this.project_id - name = var.firewall.image_name + name = var.firewall.name boot_volume = { source_type = "volume" source_id = stackit_volume.firewall[0].volume_id diff --git a/src/modules/connectivity/variables.tf b/src/modules/connectivity/variables.tf index 82ff9fd..15ba03b 100644 --- a/src/modules/connectivity/variables.tf +++ b/src/modules/connectivity/variables.tf @@ -16,7 +16,7 @@ variable "firewall" { type = object({ zone = string flavor = string - image_name = string + name = string volume_performance_class = optional(string, "storage_premium_perf4") volume_size = optional(number, 16) lan_network_range = string diff --git a/src/modules/landing-zone/variables.tf b/src/modules/landing-zone/variables.tf index 2bf120c..797f6e3 100644 --- a/src/modules/landing-zone/variables.tf +++ b/src/modules/landing-zone/variables.tf @@ -85,7 +85,7 @@ variable "dns_zone_name" { } variable "secretsmanager_acls" { - type = list(string) + type = list(string) description = "List of ACL rules for the Secrets Manager instance. Set to empty list for no ACLs or null to skip Secrets Manager creation." - default = [] + default = [] } \ No newline at end of file diff --git a/src/tests/hub_spoke_firewall.tftest.hcl b/src/tests/hub_spoke_firewall.tftest.hcl index 4064ee6..de1a06c 100644 --- a/src/tests/hub_spoke_firewall.tftest.hcl +++ b/src/tests/hub_spoke_firewall.tftest.hcl @@ -60,7 +60,7 @@ variables { flavor = "c1.2" lan_network_range = "10.0.0.0/28" wan_network_range = "10.0.0.16/28" - image_name = "pfsense-2.7.2" + name = "pfsense-2.7.2" } } diff --git a/src/variables.tf b/src/variables.tf index 6fc1d49..ae6c2d0 100644 --- a/src/variables.tf +++ b/src/variables.tf @@ -124,15 +124,15 @@ variable "connectivity" { default_prefix_length = optional(number, 28) }), null) firewall = optional(object({ - zone = string - flavor = string - image_name = string + zone = string + flavor = string + name = string volume_performance_class = optional(string, "storage_premium_perf4") volume_size = optional(number, 16) - lan_network_range = string - wan_network_range = string - lan_ip = optional(string, null) - wan_ip = optional(string, null) + lan_network_range = string + wan_network_range = string + lan_ip = optional(string, null) + wan_ip = optional(string, null) }), null) }) description = "Connectivity configuration including DNS zones, network area, and firewall. Set firewall/network_area to null to skip deployment." From 49eb0a73b1b9ad3bef16586b6551f6beb5570a5f Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:32:31 +0200 Subject: [PATCH 13/30] Update README files and module configurations - Enhanced the connectivity module README with detailed resource and input descriptions. - Updated devops module README to reflect new stackit version and improved input descriptions. - Revised governance module README to include new requirements and input fields. - Improved landing-zone module README with additional inputs and outputs for better clarity. - Updated management module README to include observability configurations and updated stackit version. - Refined sandboxes module README to standardize naming conventions for inputs. - Adjusted resource manager folder configurations in the main README for consistency. --- .terraform-docs.yml | 17 ------- .tflint.hcl | 20 --------- src/README.md | 5 ++- src/modules/connectivity/README.md | 71 ++++++++++++++++++++++++++++++ src/modules/devops/README.md | 22 ++++----- src/modules/governance/README.md | 21 ++++----- src/modules/landing-zone/README.md | 36 ++++++++++----- src/modules/management/README.md | 28 ++++++++---- src/modules/sandboxes/README.md | 19 ++++---- 9 files changed, 148 insertions(+), 91 deletions(-) delete mode 100644 .terraform-docs.yml delete mode 100644 .tflint.hcl create mode 100644 src/modules/connectivity/README.md diff --git a/.terraform-docs.yml b/.terraform-docs.yml deleted file mode 100644 index 3647d5e..0000000 --- a/.terraform-docs.yml +++ /dev/null @@ -1,17 +0,0 @@ -formatter: markdown table - -output: - file: README.md - mode: inject - -sort: - enabled: true - by: required - -settings: - anchor: true - default: true - escape: true - indent: 3 - required: true - type: true diff --git a/.tflint.hcl b/.tflint.hcl deleted file mode 100644 index c3f1a2b..0000000 --- a/.tflint.hcl +++ /dev/null @@ -1,20 +0,0 @@ -plugin "terraform" { - enabled = true - preset = "recommended" -} - -config { - call_module_type = "all" -} - -rule "terraform_required_providers" { - enabled = true -} - -rule "terraform_required_version" { - enabled = true -} - -rule "terraform_unused_declarations" { - enabled = true -} \ No newline at end of file diff --git a/src/README.md b/src/README.md index affd5bd..2e26940 100644 --- a/src/README.md +++ b/src/README.md @@ -33,16 +33,17 @@ No resources. |------|-------------|------|---------|:--------:| | [company\_code](#input\_company\_code) | Company code used in resource naming conventions. | `string` | n/a | yes | | [company\_name](#input\_company\_name) | Name of the company. | `string` | n/a | yes | -| [connectivity](#input\_connectivity) | Connectivity configuration including DNS zones, network area, and pfSense firewall. Set firewall/network\_area to null to skip deployment. |
object({
dns_zones = optional(map(object({
dns_name = string
name = optional(string, null)
contact_email = optional(string, null)
type = optional(string, "primary")
acl = optional(string, null)
description = optional(string, null)
default_ttl = optional(number, 3600)
})), {})
network_area = optional(object({
ranges = list(string)
transfer_network = string
min_prefix_length = optional(number, 24)
max_prefix_length = optional(number, 28)
default_prefix_length = optional(number, 28)
}), null)
firewall = optional(object({
zone = string
flavor = string
lan_network_range = string
wan_network_range = string
lan_ip = optional(string, null)
wan_ip = optional(string, null)
}), null)
})
| `null` | no | +| [connectivity](#input\_connectivity) | Connectivity configuration including DNS zones, network area, and firewall. Set firewall/network\_area to null to skip deployment. |
object({
dns_zones = optional(map(object({
dns_name = string
name = optional(string, null)
contact_email = optional(string, null)
type = optional(string, "primary")
acl = optional(string, null)
description = optional(string, null)
default_ttl = optional(number, 3600)
})), {})
network_area = optional(object({
ranges = list(string)
transfer_network = string
min_prefix_length = optional(number, 24)
max_prefix_length = optional(number, 28)
default_prefix_length = optional(number, 28)
}), null)
firewall = optional(object({
zone = string
flavor = string
name = string
volume_performance_class = optional(string, "storage_premium_perf4")
volume_size = optional(number, 16)
lan_network_range = string
wan_network_range = string
lan_ip = optional(string, null)
wan_ip = optional(string, null)
}), null)
})
| `null` | no | | [devops](#input\_devops) | DevOps module configuration. Set to null to skip deployment. |
object({
git_flavor = optional(string, null)
allowed_network_ranges = optional(list(string), ["0.0.0.0/0"])
})
| `null` | no | | [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no | | [landing\_zones](#input\_landing\_zones) | Map of landing zones to create. Set corporate = true for network area connectivity, false for public. |
map(object({
project_name = string
project_code = string
owner_email = string
# Set to true for corporate landing zones (connected to network area), false for public
corporate = optional(bool, true)
env = optional(string, "dev")
role_assignments = optional(list(object({
role = string
subject = string
})), [])
network_prefix_length = optional(number, null)
custom_roles = optional(list(object({
name = string
description = string
permissions = list(string)
})), [])
}))
| `{}` | no | +| [observability](#input\_observability) | Observability instance configuration for the management module. Set to null to skip observability deployment. |
object({
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
logs_retention_days = optional(number, 30)
traces_retention_days = optional(number, 30)
metrics_retention_days = optional(number, 90)
metrics_retention_days_5m_downsampling = optional(number, 90)
metrics_retention_days_1h_downsampling = optional(number, 90)
})
| `null` | no | | [organization\_auditors](#input\_organization\_auditors) | List of organization auditors. | `list(string)` | `[]` | no | | [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes | | [organization\_owners](#input\_organization\_owners) | List of organization owners. | `list(string)` | `[]` | no | | [owner\_email](#input\_owner\_email) | Email address of the owner. Required for STACKIT resource manager. | `string` | n/a | yes | | [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no | -| [rm\_folders](#input\_rm\_folders) | Map of resource manager folders to create under the root organization. |
map(object({
name = string
description = optional(string, null)
owner_emails = list(string)
reader_emails = list(string)
}))
|
{
"landing_zones_corporate": {
"name": "Landing Zones - Corporate",
"owner_emails": [],
"reader_emails": []
},
"landing_zones_public": {
"name": "Landing Zones - Public",
"owner_emails": [],
"reader_emails": []
},
"platform": {
"name": "Platform",
"owner_emails": [],
"reader_emails": []
},
"sandboxes": {
"name": "Sandboxes",
"owner_emails": [],
"reader_emails": []
}
}
| no | +| [rm\_folders](#input\_rm\_folders) | Map of resource manager folders to create under the root organization. |
map(object({
name = string
description = optional(string, null)
owner_emails = list(string)
reader_emails = list(string)
}))
|
{
"landing_zones_corporate": {
"name": "Landing Zones - Corporate 3",
"owner_emails": [],
"reader_emails": []
},
"landing_zones_public": {
"name": "Landing Zones - Public 3",
"owner_emails": [],
"reader_emails": []
},
"platform": {
"name": "Platform 3",
"owner_emails": [],
"reader_emails": []
},
"sandboxes": {
"name": "Sandboxes 3",
"owner_emails": [],
"reader_emails": []
}
}
| no | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | ## Outputs diff --git a/src/modules/connectivity/README.md b/src/modules/connectivity/README.md new file mode 100644 index 0000000..f7fa98b --- /dev/null +++ b/src/modules/connectivity/README.md @@ -0,0 +1,71 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.10 | +| [stackit](#requirement\_stackit) | >=0.93.0 | +| [time](#requirement\_time) | >= 0.13.0 | + +## Providers + +| Name | Version | +|------|---------| +| [stackit](#provider\_stackit) | 0.93.0 | +| [time](#provider\_time) | >= 0.13.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [stackit_authorization_project_role_assignment.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_project_role_assignment) | resource | +| [stackit_dns_zone.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/dns_zone) | resource | +| [stackit_image.firewall](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/image) | resource | +| [stackit_network.lan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network) | resource | +| [stackit_network.wan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network) | resource | +| [stackit_network_area.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_area) | resource | +| [stackit_network_area_region.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_area_region) | resource | +| [stackit_network_interface.lan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_interface) | resource | +| [stackit_network_interface.wan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_interface) | resource | +| [stackit_public_ip.wan-ip](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/public_ip) | resource | +| [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | +| [stackit_routing_table.wan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/routing_table) | resource | +| [stackit_routing_table_route.wan](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/routing_table_route) | resource | +| [stackit_server.firewall](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/server) | resource | +| [stackit_volume.firewall](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/volume) | resource | +| [time_sleep.wait_before_network_area_region_destroy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [time_sleep.wait_for_network_area](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [dns\_zones](#input\_dns\_zones) | Map of DNS zone keys to DNS zone configuration. Name defaults to dns\_name if not set. |
map(object({
dns_name = string
name = optional(string, null)
contact_email = optional(string, null)
type = optional(string, "primary")
acl = optional(string, null)
description = optional(string, null)
default_ttl = optional(number, 3600)
}))
| `{}` | no | +| [firewall](#input\_firewall) | Firewall configuration. Set to null to skip firewall deployment (network area and routing are still created). lan\_network\_range and wan\_network\_range must be CIDRs within the network area range. lan\_ip and wan\_ip are optional; when omitted, the 5th address of the respective prefix is used (STACKIT reserves the first usable address as the gateway). |
object({
zone = string
flavor = string
name = string
volume_performance_class = optional(string, "storage_premium_perf4")
volume_size = optional(number, 16)
lan_network_range = string
wan_network_range = string
lan_ip = optional(string, null)
wan_ip = optional(string, null)
})
| `null` | no | +| [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | +| [network\_area](#input\_network\_area) | Network area configuration including IP ranges, transfer network, and prefix length settings. |
object({
ranges = list(string)
transfer_network = string
min_prefix_length = optional(number, 24)
max_prefix_length = optional(number, 28)
default_prefix_length = optional(number, 28)
default_nameservers = optional(list(string), ["1.0.0.1", "1.1.1.1"])
})
| n/a | yes | +| [network\_area\_name](#input\_network\_area\_name) | Name of the network area to create for this region. | `string` | `null` | no | +| [organization\_id](#input\_organization\_id) | Organization ID, required for network area and route configuration. | `string` | n/a | yes | +| [owner\_email](#input\_owner\_email) | Email address of the owner for the project. Required for STACKIT resource manager. | `string` | n/a | yes | +| [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | +| [project\_name](#input\_project\_name) | Name of the STACKIT project to create. Falls back to naming\_pattern if not set. | `string` | `null` | no | +| [role\_assignments](#input\_role\_assignments) | List of role assignments for the project. Subject can be a user email or service account email. |
list(object({
role = string
subject = string
}))
| `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [dns\_zone\_dns\_names](#output\_dns\_zone\_dns\_names) | Map of DNS zone keys to their DNS names | +| [dns\_zone\_ids](#output\_dns\_zone\_ids) | Map of DNS zone keys to their zone IDs | +| [firewall\_next\_hop\_ip](#output\_firewall\_next\_hop\_ip) | The IP address to be used as next hop for the default route in the landing zones (firewall WAN IP). | +| [firewall\_public\_ip](#output\_firewall\_public\_ip) | The public IP address of the firewall WAN interface. | +| [network\_area\_id](#output\_network\_area\_id) | The ID of the created network area. | +| [project\_container\_id](#output\_project\_container\_id) | The container ID of the created STACKIT project. | +| [project\_id](#output\_project\_id) | The project ID of the created STACKIT project. | +| [project\_name](#output\_project\_name) | The name of the created STACKIT project. | + \ No newline at end of file diff --git a/src/modules/devops/README.md b/src/modules/devops/README.md index 3e0c95e..6577001 100644 --- a/src/modules/devops/README.md +++ b/src/modules/devops/README.md @@ -1,22 +1,22 @@ -### Requirements +## Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.88.0 | +| [stackit](#requirement\_stackit) | >=0.93.0 | -### Providers +## Providers | Name | Version | |------|---------| | [stackit](#provider\_stackit) | 0.88.0 | -### Modules +## Modules No modules. -### Resources +## Resources | Name | Type | |------|------| @@ -24,22 +24,22 @@ No modules. | [stackit_git.git](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/git) | resource | | [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | -### Inputs +## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [company\_name](#input\_company\_name) | Name of the company folder to create. | `string` | n/a | yes | -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | -| [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | -| [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | | [allowed\_network\_ranges](#input\_allowed\_network\_ranges) | List of allowed network ranges for Git instance ACL. | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [company\_name](#input\_company\_name) | Name of the company folder to create. | `string` | n/a | yes | | [git\_flavor](#input\_git\_flavor) | The flavor of the Git instance. | `string` | `null` | no | | [labels](#input\_labels) | Additional labels to apply to all folders. | `map(string)` | `{}` | no | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | | [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if network is enabled. | `string` | `null` | no | +| [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | +| [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | | [project\_name](#input\_project\_name) | Name of the STACKIT project to create. | `string` | `null` | no | | [role\_assignments](#input\_role\_assignments) | List of role assignments for the project. Subject can be a user email or service account email. |
list(object({
role = string
subject = string
}))
| `[]` | no | -### Outputs +## Outputs | Name | Description | |------|-------------| diff --git a/src/modules/governance/README.md b/src/modules/governance/README.md index 537d4a2..2bfd363 100644 --- a/src/modules/governance/README.md +++ b/src/modules/governance/README.md @@ -1,22 +1,23 @@ -### Requirements +## Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.88.0 | +| [stackit](#requirement\_stackit) | >=0.93.0 | +| [time](#requirement\_time) | >= 0.13.0 | -### Providers +## Providers | Name | Version | |------|---------| -| [stackit](#provider\_stackit) | 0.88.0 | +| [stackit](#provider\_stackit) | 0.93.0 | -### Modules +## Modules No modules. -### Resources +## Resources | Name | Type | |------|------| @@ -27,19 +28,19 @@ No modules. | [stackit_authorization_project_custom_role.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_project_custom_role) | resource | | [stackit_resourcemanager_folder.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_folder) | resource | -### Inputs +## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [organization\_id](#input\_organization\_id) | Container ID of the root folder or organization under which the company folder will be created. | `string` | n/a | yes | -| [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | | [custom\_roles](#input\_custom\_roles) | List of custom roles to create at the organization level. |
list(object({
name = string
description = string
permissions = list(string)
}))
| `[]` | no | | [labels](#input\_labels) | Additional labels to apply to all folders. | `map(string)` | `{}` | no | | [organization\_auditors](#input\_organization\_auditors) | List of organization role assignments for organization auditors. | `list(string)` | `[]` | no | +| [organization\_id](#input\_organization\_id) | Container ID of the root folder or organization under which the company folder will be created. | `string` | n/a | yes | | [organization\_owners](#input\_organization\_owners) | List of organization role assignments for organization owners. | `list(string)` | `[]` | no | +| [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | | [rm\_folders](#input\_rm\_folders) | Map of folder keys to folder configuration. Each folder has a display name and optional lists of owner and reader subjects. |
map(object({
name = string
owner_emails = optional(list(string), [])
reader_emails = optional(list(string), [])
}))
|
{
"landing_zones_corporate": {
"name": "Landing Zones - Corporate",
"owner_emails": [],
"reader_emails": []
},
"landing_zones_public": {
"name": "Landing Zones - Public",
"owner_emails": [],
"reader_emails": []
},
"platform": {
"name": "Platform",
"owner_emails": [],
"reader_emails": []
},
"sandbox": {
"name": "Sandboxes",
"owner_emails": [],
"reader_emails": []
}
}
| no | -### Outputs +## Outputs | Name | Description | |------|-------------| diff --git a/src/modules/landing-zone/README.md b/src/modules/landing-zone/README.md index 3f3b3f8..a340f21 100644 --- a/src/modules/landing-zone/README.md +++ b/src/modules/landing-zone/README.md @@ -1,60 +1,72 @@ -### Requirements +## Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5 | -| [stackit](#requirement\_stackit) | >=0.88.0 | +| [stackit](#requirement\_stackit) | >=0.93.0 | | [time](#requirement\_time) | >=0.13.1 | -### Providers +## Providers | Name | Version | |------|---------| -| [stackit](#provider\_stackit) | 0.88.0 | +| [stackit](#provider\_stackit) | 0.93.0 | | [time](#provider\_time) | 0.13.1 | -### Modules +## Modules No modules. -### Resources +## Resources | Name | Type | |------|------| | [stackit_authorization_project_custom_role.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_project_custom_role) | resource | | [stackit_authorization_project_role_assignment.sa_owner](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_project_role_assignment) | resource | | [stackit_authorization_project_role_assignment.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_project_role_assignment) | resource | +| [stackit_dns_zone.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/dns_zone) | resource | | [stackit_network.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network) | resource | | [stackit_objectstorage_bucket.default](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_bucket) | resource | | [stackit_objectstorage_bucket.tfstate](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_bucket) | resource | | [stackit_objectstorage_credential.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_credential) | resource | | [stackit_objectstorage_credentials_group.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_credentials_group) | resource | | [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | +| [stackit_routing_table.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/routing_table) | resource | +| [stackit_routing_table_route.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/routing_table_route) | resource | | [stackit_secretsmanager_instance.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/secretsmanager_instance) | resource | -| [stackit_secretsmanager_user.default](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/secretsmanager_user) | resource | | [stackit_service_account.automation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/service_account) | resource | | [stackit_service_account_key.automation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/service_account_key) | resource | | [time_rotating.key_rotate](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/rotating) | resource | -### Inputs +## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [corporate](#input\_corporate) | Whether this landing zone uses corporate networking (network area + routing). Set to false for public internet. | `bool` | `false` | no | | [custom\_roles](#input\_custom\_roles) | List of custom roles to create for the project. |
list(object({
name = string
description = string
permissions = list(string)
}))
| n/a | yes | +| [dns\_zone\_name](#input\_dns\_zone\_name) | Full DNS zone domain name for this landing zone. Set to null to skip DNS zone creation. | `string` | `null` | no | +| [firewall\_next\_hop\_ip](#input\_firewall\_next\_hop\_ip) | IP address of the firewall next hop. | `string` | `null` | no | +| [ipv4\_nameservers](#input\_ipv4\_nameservers) | List of IPv4 nameservers for the network. | `list(string)` | `null` | no | +| [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no | | [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | +| [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if corporate is true. | `string` | `null` | no | +| [network\_prefix\_length](#input\_network\_prefix\_length) | CIDR block prefix length for the project's network range. | `number` | `null` | no | +| [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes | | [owner\_email](#input\_owner\_email) | Email address of the project owner. Required for project creation. | `string` | n/a | yes | | [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | -| [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no | -| [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if network is enabled. | `string` | `null` | no | -| [network\_prefix\_length](#input\_network\_prefix\_length) | CIDR block prefix length for the project's network range. | `number` | `null` | no | | [project\_name](#input\_project\_name) | Name of the STACKIT project to create. | `string` | `null` | no | | [role\_assignments](#input\_role\_assignments) | List of role assignments for the project. Subject can be a user email or service account email. |
list(object({
role = string
subject = string
}))
| `[]` | no | +| [secretsmanager\_acls](#input\_secretsmanager\_acls) | List of ACL rules for the Secrets Manager instance. Set to empty list for no ACLs or null to skip Secrets Manager creation. | `list(string)` | `[]` | no | -### Outputs +## Outputs | Name | Description | |------|-------------| +| [connected\_network\_area\_id](#output\_connected\_network\_area\_id) | The ID of the connected network area. | +| [dns\_zone\_dns\_name](#output\_dns\_zone\_dns\_name) | The DNS name of the landing zone's child DNS zone. | +| [dns\_zone\_id](#output\_dns\_zone\_id) | The ID of the landing zone's child DNS zone. | +| [landing\_zone\_type](#output\_landing\_zone\_type) | The type of the landing zone, either 'corporate' or 'public'. | | [project\_container\_id](#output\_project\_container\_id) | The container ID of the created STACKIT project. | | [project\_id](#output\_project\_id) | The project ID of the created STACKIT project. | | [project\_name](#output\_project\_name) | The name of the created STACKIT project. | diff --git a/src/modules/management/README.md b/src/modules/management/README.md index 31ffca2..5e9411d 100644 --- a/src/modules/management/README.md +++ b/src/modules/management/README.md @@ -1,24 +1,26 @@ -### Requirements +## Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.88.0 | +| [stackit](#requirement\_stackit) | >=0.93.0 | | [time](#requirement\_time) | >=0.13.1 | +| [vault](#requirement\_vault) | >=5.7.0 | -### Providers +## Providers | Name | Version | |------|---------| -| [stackit](#provider\_stackit) | 0.88.0 | +| [stackit](#provider\_stackit) | 0.93.0 | | [time](#provider\_time) | 0.13.1 | +| [vault](#provider\_vault) | 5.9.0 | -### Modules +## Modules No modules. -### Resources +## Resources | Name | Type | |------|------| @@ -28,31 +30,39 @@ No modules. | [stackit_objectstorage_bucket.tfstate](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_bucket) | resource | | [stackit_objectstorage_credential.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_credential) | resource | | [stackit_objectstorage_credentials_group.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_credentials_group) | resource | +| [stackit_observability_credential.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/observability_credential) | resource | +| [stackit_observability_instance.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/observability_instance) | resource | | [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | | [stackit_secretsmanager_instance.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/secretsmanager_instance) | resource | | [stackit_secretsmanager_user.default](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/secretsmanager_user) | resource | | [stackit_service_account.automation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/service_account) | resource | | [stackit_service_account_key.automation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/service_account_key) | resource | | [time_rotating.key_rotate](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/rotating) | resource | +| [vault_kv_secret_v2.object_storage_credentials](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/kv_secret_v2) | resource | +| [vault_kv_secret_v2.observability](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/kv_secret_v2) | resource | +| [vault_kv_secret_v2.service_account_key_automation](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/kv_secret_v2) | resource | -### Inputs +## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [labels](#input\_labels) | Additional labels to apply to all folders. | `map(string)` | `{}` | no | | [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | +| [observability](#input\_observability) | Observability instance configuration. Set to null to skip observability deployment. |
object({
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
logs_retention_days = optional(number, 30)
traces_retention_days = optional(number, 30)
metrics_retention_days = optional(number, 90)
metrics_retention_days_5m_downsampling = optional(number, 90)
metrics_retention_days_1h_downsampling = optional(number, 90)
})
| `null` | no | | [organization\_id](#input\_organization\_id) | Container ID of the root folder or organization under which the company folder will be created. | `string` | n/a | yes | | [owner\_email](#input\_owner\_email) | Email address of the owner for the folders. Required for STACKIT resource manager. | `string` | n/a | yes | | [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | -| [labels](#input\_labels) | Additional labels to apply to all folders. | `map(string)` | `{}` | no | | [project\_name](#input\_project\_name) | Name of the STACKIT project to create. | `string` | `null` | no | | [role\_assignments](#input\_role\_assignments) | List of role assignments for the project. Subject can be a user email or service account email. |
list(object({
role = string
subject = string
}))
| `[]` | no | -### Outputs +## Outputs | Name | Description | |------|-------------| | [project\_container\_id](#output\_project\_container\_id) | The container ID of the created STACKIT project. | | [project\_id](#output\_project\_id) | The project ID of the created STACKIT project. | | [project\_name](#output\_project\_name) | The name of the created STACKIT project. | +| [secretsmanager\_password](#output\_secretsmanager\_password) | The password of the default Secrets Manager user. | +| [secretsmanager\_username](#output\_secretsmanager\_username) | The username of the default Secrets Manager user. | | [service\_account\_email](#output\_service\_account\_email) | The email of the created service account. | \ No newline at end of file diff --git a/src/modules/sandboxes/README.md b/src/modules/sandboxes/README.md index 355c527..9f87852 100644 --- a/src/modules/sandboxes/README.md +++ b/src/modules/sandboxes/README.md @@ -1,38 +1,37 @@ -### Requirements +## Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.88.0 | +| [stackit](#requirement\_stackit) | >=0.93.0 | -### Providers +## Providers | Name | Version | |------|---------| -| [stackit](#provider\_stackit) | 0.88.0 | +| [stackit](#provider\_stackit) | 0.93.0 | -### Modules +## Modules No modules. -### Resources +## Resources | Name | Type | |------|------| | [stackit_authorization_project_role_assignment.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_project_role_assignment) | resource | | [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | -### Inputs +## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes | +| [naming\_prefix](#input\_naming\_prefix) | Naming prefix for all resources in this module. | `string` | n/a | yes | | [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes | -| [project\_name](#input\_project\_name) | Name of the STACKIT project to create. | `string` | `null` | no | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | -### Outputs +## Outputs | Name | Description | |------|-------------| From 62128434ab226db210d4b44628f73b9c2429e22a Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:48:50 +0200 Subject: [PATCH 14/30] Add STACKIT logo SVG and update README for improved presentation - Added stackit-logo.svg to the .github/images directory. - Updated README.md to include the STACKIT logo at the top, enhancing visual appeal. - Removed unnecessary emojis from section headers for a cleaner look. - Adjusted section titles and content for better organization and clarity. --- .github/images/stackit-logo.svg | 43 ++++++++++++++++++++++ README.md | 65 ++++++--------------------------- 2 files changed, 55 insertions(+), 53 deletions(-) create mode 100644 .github/images/stackit-logo.svg diff --git a/.github/images/stackit-logo.svg b/.github/images/stackit-logo.svg new file mode 100644 index 0000000..6823680 --- /dev/null +++ b/.github/images/stackit-logo.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index c509bd2..8a4ff08 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -# ๐Ÿš€ STACKIT Landing Zone Accelerator +
+
+STACKIT logo +
+
+
+ +# Landing Zone Accelerator **Accelerate your STACKIT cloud adoption with production-ready, modular landing zones.** @@ -9,48 +16,15 @@ --- -## โœจ Overview +## Overview The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based framework for deploying secure, scalable, and well-architected cloud environments on STACKIT. Built with enterprise best practices, it enables teams to quickly establish governance, networking, and security foundations. -## ๐ŸŽฏ Key Features - -- **๐Ÿ—๏ธ Modular Architecture** โ€” Compose your infrastructure using reusable, tested Terraform modules -- **๐Ÿ” Security First** โ€” Pre-configured RBAC, secrets management, and network segmentation -- **๐Ÿ“ Three Deployment Flavours** โ€” Start standalone and evolve to hub-spoke with or without a firewall -- **๐ŸŒ Multi-Environment** โ€” Seamlessly manage production and non-production workloads -- **โšก Quick Start** โ€” Get up and running in minutes with sensible defaults - -## ๐Ÿ“ฆ Modules - -| Module | Description | -|--------|-------------| -| `connectivity` | Connectivity hub project with network area, WAN/LAN networks, optional pfSense firewall, and DNS zones | -| `devops` | DevOps project with RBAC and managed Git instance | -| `governance` | Resource Manager folder hierarchy, custom roles, and organization-level role assignments | -| `landing-zone` | Landing zone project with RBAC, networking, Secrets Manager, Object Storage, and service accounts | -| `management` | Management project with Secrets Manager, Object Storage, service accounts, and observability | -| `sandboxes` | Sandbox projects with RBAC role assignments for experimentation workloads | - -## ๐Ÿš€ Getting Started - -```bash -# Clone the repository -git clone https://github.com/stackitcloud/stackit-landing-zone.git -cd stackit-landing-zone/src - -# Copy and edit the tfvars for your desired flavour (see Deployment Flavours below) -cp config/standalone.tfvars terraform.auto.tfvars - -# Initialize and deploy -tofu init -tofu plan -tofu apply -``` +## ๐Ÿ“š Documentation -๐Ÿ“– See the [Getting Started Guide](docs/getting-started.md) for detailed instructions. +- [Getting Started](docs/getting-started.md) -## ๐Ÿ—‚๏ธ Deployment Flavours +## Deployment Flavours Three ready-to-use configurations are provided in `src/config/`: @@ -60,21 +34,6 @@ Three ready-to-use configurations are provided in `src/config/`: | **Hub-Spoke** | `hub-and-spoke.tfvars` | Adds a connectivity hub with a network area and DNS zones. Corporate landing zones connect via the network area. | | **Hub-Spoke + Firewall** | `hub-and-spoke-firewall.tfvars` | Full hub-spoke topology with a pfSense firewall appliance on the WAN/LAN boundary. | -## ๐Ÿงช Testing - -Tests for all three flavours are located in `src/tests/` and use the native OpenTofu test framework: - -```bash -cd src -tofu test -``` - -| Test file | Flavour tested | -|-----------|---------------| -| `standalone.tftest.hcl` | Standalone โ€” no connectivity module | -| `hub_spoke.tftest.hcl` | Hub-spoke without and with firewall (two runs) | -| `hub_spoke_firewall.tftest.hcl` | Hub-spoke with firewall | - ## ๐Ÿ“š Documentation - [Getting Started](docs/getting-started.md) From 6dcc033fac3ce3e186423e080103d2c3b5842645 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:49:39 +0200 Subject: [PATCH 15/30] chore: update README to add overview section separator --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a4ff08..df4b1bc 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ --- -## Overview - The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based framework for deploying secure, scalable, and well-architected cloud environments on STACKIT. Built with enterprise best practices, it enables teams to quickly establish governance, networking, and security foundations. +--- + ## ๐Ÿ“š Documentation - [Getting Started](docs/getting-started.md) From 025ec64dfe75bcc7b30dbe444c871732a47c2e22 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:50:10 +0200 Subject: [PATCH 16/30] chore: remove unnecessary horizontal rule from README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index df4b1bc..080949e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based framework for deploying secure, scalable, and well-architected cloud environments on STACKIT. Built with enterprise best practices, it enables teams to quickly establish governance, networking, and security foundations. ---- ## ๐Ÿ“š Documentation From 835b4eaeb1897c54e0e9df93a08bcfa6689c2338 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:51:04 +0200 Subject: [PATCH 17/30] chore: remove promotional text and horizontal rule from README --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 080949e..1b41cce 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,13 @@ # Landing Zone Accelerator -**Accelerate your STACKIT cloud adoption with production-ready, modular landing zones.** - [![Terraform](https://img.shields.io/badge/Terraform-1.10+-623CE4?logo=terraform&logoColor=white)](https://www.terraform.io/) [![OpenTofu](https://img.shields.io/badge/OpenTofu-1.11+-FFDA18?logo=opentofu&logoColor=black)](https://opentofu.org/) [![STACKIT](https://img.shields.io/badge/STACKIT-Cloud-00A9E0)](https://www.stackit.de/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) ---- - The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based framework for deploying secure, scalable, and well-architected cloud environments on STACKIT. Built with enterprise best practices, it enables teams to quickly establish governance, networking, and security foundations. - ## ๐Ÿ“š Documentation - [Getting Started](docs/getting-started.md) From 6d1e9f92934c7e21a4a3c2d1adefe04f158b23d3 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:52:46 +0200 Subject: [PATCH 18/30] chore: update Terraform and STACKIT badge colors in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b41cce..acf07d5 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ # Landing Zone Accelerator -[![Terraform](https://img.shields.io/badge/Terraform-1.10+-623CE4?logo=terraform&logoColor=white)](https://www.terraform.io/) +[![Terraform](https://img.shields.io/badge/Terraform-1.15+-623CE4?logo=terraform&logoColor=white)](https://www.terraform.io/) [![OpenTofu](https://img.shields.io/badge/OpenTofu-1.11+-FFDA18?logo=opentofu&logoColor=black)](https://opentofu.org/) -[![STACKIT](https://img.shields.io/badge/STACKIT-Cloud-00A9E0)](https://www.stackit.de/) +[![STACKIT](https://img.shields.io/badge/STACKIT-Cloud-004E5A)](https://www.stackit.de/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based framework for deploying secure, scalable, and well-architected cloud environments on STACKIT. Built with enterprise best practices, it enables teams to quickly establish governance, networking, and security foundations. From 02bc777590a7f68275a3211dca5fca2ce31a4be0 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 14:55:40 +0200 Subject: [PATCH 19/30] chore: remove Deployment Flavours section and related documentation from README --- README.md | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/README.md b/README.md index acf07d5..7af5d00 100644 --- a/README.md +++ b/README.md @@ -18,38 +18,6 @@ The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based fr - [Getting Started](docs/getting-started.md) -## Deployment Flavours - -Three ready-to-use configurations are provided in `src/config/`: - -| Flavour | Config file | Description | -|---------|-------------|-------------| -| **Standalone** | `standalone.tfvars` | Governance, management, devops, and public landing zones only. No network area or firewall. | -| **Hub-Spoke** | `hub-and-spoke.tfvars` | Adds a connectivity hub with a network area and DNS zones. Corporate landing zones connect via the network area. | -| **Hub-Spoke + Firewall** | `hub-and-spoke-firewall.tfvars` | Full hub-spoke topology with a pfSense firewall appliance on the WAN/LAN boundary. | - -## ๐Ÿ“š Documentation - -- [Getting Started](docs/getting-started.md) -- [Deployment Guide](docs/deployment-guide.md) - -## ๐Ÿ” Linting (TFLint) - -Run locally: - -```bash -tflint --init -tflint --recursive -``` - -Variable validations enforce flavor naming patterns for `firewall.flavor` (connectivity module) and `git_flavor` (devops module). Use `stackit server machine-type list` and the STACKIT Git API docs to verify currently available flavors. - -For live validation against current STACKIT SKUs: - -```bash -python3 scripts/validate_stackit_flavors.py -``` - ## ๐Ÿค Contributing Contributions are welcome! Please feel free to submit a Pull Request. From 0d077148c7ee7a0f168ddbbb33ab376328e04868 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 15:10:48 +0200 Subject: [PATCH 20/30] chore: add Getting Started guide with deployment instructions and prerequisites Co-authored-by: Copilot --- docs/deployment-guide.md | 0 docs/getting-started.md | 204 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) delete mode 100644 docs/deployment-guide.md diff --git a/docs/deployment-guide.md b/docs/deployment-guide.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/getting-started.md b/docs/getting-started.md index e69de29..9aa09fb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -0,0 +1,204 @@ +# Getting Started + +This guide walks you through deploying the STACKIT Landing Zone from scratch. + +--- + +## Prerequisites + +- A **STACKIT organization** with your user account registered +- **Owner permissions** on the STACKIT organization +- **STACKIT CLI** installed ([Installation guide](https://github.com/stackitcloud/stackit-cli/blob/main/INSTALLATION.md)) +- **OpenTofu** (>= 1.10) or **Terraform** (>= 1.10) installed + +--- + +## Deployment Flavours + +Three ready-to-use configurations are provided in `src/config/`: + +| Flavour | Config file | Description | +|---------|-------------|-------------| +| **Standalone** | `standalone.tfvars` | Governance, management, devops, and public landing zones only. No network area or firewall. | +| **Hub-Spoke** | `hub-and-spoke.tfvars` | Adds a connectivity hub with a network area and DNS zones. Corporate landing zones connect via the network area. | +| **Hub-Spoke + Firewall** | `hub-and-spoke-firewall.tfvars` | Full hub-spoke topology with a pfSense firewall appliance on the WAN/LAN boundary. | + +Choose the flavour that matches your requirements and adjust the corresponding `.tfvars` file before deployment. At a minimum, update `owner_email`, `organization_id`, `company_name`, and `company_code`. + +--- + +## Step-by-Step Deployment + +### 1. Clone the repository + +```bash +git clone https://github.com/stackitcloud/stackit-landing-zone.git +cd stackit-landing-zone/src +``` + +### 2. Download the pfSense firewall image (Hub-Spoke + Firewall only) + +If you are deploying the Hub-Spoke + Firewall flavour, download the pfSense image into the `src/` directory: + +```bash +curl -o firewall-image.qcow2 https://pfsense.object.storage.eu01.onstackit.cloud/pfsense-ce-2.7.2-amd64-10-12-2024.qcow2 +``` + +### 3. Authenticate with STACKIT + +Log in interactively via browser: + +```bash +stackit auth login +``` + +### 4. Create a temporary bootstrap project + +A short-lived project is needed to create the initial service account for Terraform/OpenTofu authentication: + +```bash +stackit project create tmp-bootstrap +``` + +Note the `project_id` from the output. + +### 5. Create a bootstrap service account + +```bash +stackit service-account create bootstrap-sa --project-id +``` + +Grant the service account **owner** permissions at the **organization level** so it can provision all resources: + +```bash +stackit organization member add --subject --role organization.owner +``` + +### 6. Configure service account credentials + +Create a service account key and configure it for the STACKIT Terraform provider. Set the following environment variables: + +```bash +export STACKIT_SERVICE_ACCOUNT_EMAIL= +export STACKIT_SERVICE_ACCOUNT_TOKEN= +``` + +Refer to the [STACKIT Terraform provider documentation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs) for all supported authentication methods. + +### 7. Configure variables + +Copy and edit the `.tfvars` file matching your chosen deployment flavour: + +```bash +cp config/standalone.tfvars terraform.tfvars +``` + +Update the values to match your organization. Required variables: + +| Variable | Description | +|----------|-------------| +| `owner_email` | Technical owner email registered in STACKIT | +| `company_name` | Company name for folder naming | +| `company_code` | Short prefix for resource naming (e.g. `exc`) | +| `organization_id` | Root organization container ID | + +### 8. Initialize OpenTofu/Terraform + +Comment out or remove the `backend "s3"` block in `backend.tf` for the initial run (state will be stored locally): + +```bash +tofu init +``` + +### 9. Deploy the landing zone + +```bash +tofu apply -var-file=config/.tfvars +``` + +Replace `` with `standalone`, `hub-and-spoke`, or `hub-and-spoke-firewall`. + +Review the plan and confirm with `yes`. + +--- + +## Migrating State to the Created Backend + +After the first successful apply, the management module has created an S3 bucket for remote state and a service account for ongoing automation. Migrate to this backend to enable team collaboration and state locking. + +### 10. Enable the S3 backend + +Uncomment the `backend "s3"` block in `backend.tf` and update the `bucket` name to match the Terraform output (format: `-pltfm-mgmt-prod-tfstate`). Configure the bucket credentials from the Secrets Manager in the management project: + +```hcl +terraform { + backend "s3" { + bucket = "-pltfm-mgmt-prod-tfstate" + endpoints = { + s3 = "https://object.storage.eu01.onstackit.cloud" + } + key = "terraform.tfstate" + region = "eu01" + skip_credentials_validation = true + skip_region_validation = true + skip_requesting_account_id = true + skip_s3_checksum = true + } +} +``` + +Set the S3 backend credentials (retrieved from the Secrets Manager instance in the management project): + +```bash +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +``` + +### 11. Migrate state + +```bash +tofu init -migrate-state +``` + +Confirm the migration when prompted. + +### 12. Switch to the management service account + +Replace the bootstrap credentials with the service account created by the management module. The service account email is available in the Terraform outputs. Retrieve its credentials from the Secrets Manager instance in the management project. + +Update the environment variables: + +```bash +export STACKIT_SERVICE_ACCOUNT_EMAIL= +export STACKIT_SERVICE_ACCOUNT_TOKEN= +``` + +### 13. Verify the migration + +Run a plan to confirm no changes are detected: + +```bash +tofu plan -var-file=config/.tfvars +``` + +The output should show `No changes. Your infrastructure matches the configuration.` + +--- + +## Cleanup + +### 14. Delete the bootstrap project + +The temporary bootstrap project is no longer needed: + +```bash +stackit project delete +``` + +--- + +## Post-Deployment (Optional) + +### Configure pfSense firewall + +If you deployed the Hub-Spoke + Firewall flavour, configure the pfSense appliance as described in the [STACKIT pfSense documentation](https://docs.stackit.cloud/products/quick-deployments/pfsense-firewall/tutorials/configure-pfsense/). \ No newline at end of file From 8afb07cd8f5af039f6c1e24d500fd207e93768cf Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 15:34:53 +0200 Subject: [PATCH 21/30] chore: update getting started guide with improved project creation instructions and add mise.toml for tool dependencies --- docs/getting-started.md | 21 +++++++++++---------- mise.toml | 3 +++ 2 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 mise.toml diff --git a/docs/getting-started.md b/docs/getting-started.md index 9aa09fb..08376dc 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -57,7 +57,11 @@ stackit auth login A short-lived project is needed to create the initial service account for Terraform/OpenTofu authentication: ```bash -stackit project create tmp-bootstrap +# get the organization id +stackit organization list + +# create the project +stackit project create --name tmp-bootstrap --parent-id ``` Note the `project_id` from the output. @@ -65,13 +69,10 @@ Note the `project_id` from the output. ### 5. Create a bootstrap service account ```bash -stackit service-account create bootstrap-sa --project-id -``` +stackit service-account create --name bootstrap-sa --project-id -Grant the service account **owner** permissions at the **organization level** so it can provision all resources: - -```bash -stackit organization member add --subject --role organization.owner +# Grant the service account owner permissions at the organization level so it can provision all resources: +stackit organization member add --role organization.owner --organization-id ``` ### 6. Configure service account credentials @@ -90,7 +91,7 @@ Refer to the [STACKIT Terraform provider documentation](https://registry.terrafo Copy and edit the `.tfvars` file matching your chosen deployment flavour: ```bash -cp config/standalone.tfvars terraform.tfvars +cp config/standalone.tfvars terraform.auto.tfvars ``` Update the values to match your organization. Required variables: @@ -113,10 +114,10 @@ tofu init ### 9. Deploy the landing zone ```bash -tofu apply -var-file=config/.tfvars +tofu apply ``` -Replace `` with `standalone`, `hub-and-spoke`, or `hub-and-spoke-firewall`. +Hint: if you didnt suffix your tfvars file with .auto.tfvars run `tofu apply -var-file ./config` Review the plan and confirm with `yes`. diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..2d0927d --- /dev/null +++ b/mise.toml @@ -0,0 +1,3 @@ +[tools] +opentofu = "1.11.6" +"github:stackitcloud/stackit-cli" = "0.61.0" \ No newline at end of file From 820eb626f3e0cc2a5d6921d67b209ad470328283 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 15:46:37 +0200 Subject: [PATCH 22/30] chore: update service account configuration instructions in Getting Started guide --- docs/getting-started.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 08376dc..2af33e6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -77,11 +77,11 @@ stackit organization member add --role organization.owne ### 6. Configure service account credentials -Create a service account key and configure it for the STACKIT Terraform provider. Set the following environment variables: +Create a service account key and configure it for the STACKIT Terraform provider: ```bash -export STACKIT_SERVICE_ACCOUNT_EMAIL= -export STACKIT_SERVICE_ACCOUNT_TOKEN= +mkdir -p ~/.stackit +stackit service-account key create --email bootstrap-sa-ap82bsi8@sa.stackit.cloud --project-id -y --verbosity error > ~/.stackit/credentials.json ``` Refer to the [STACKIT Terraform provider documentation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs) for all supported authentication methods. From 33ff7d76553da9b9e0480bb38041eb079257d6f2 Mon Sep 17 00:00:00 2001 From: Simon Peppel Date: Tue, 5 May 2026 15:56:09 +0200 Subject: [PATCH 23/30] chore: use STACKIT_SERVICE_ACCOUNT_KEY_PATH --- mise.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mise.toml b/mise.toml index 2d0927d..46d8a0b 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,6 @@ [tools] opentofu = "1.11.6" -"github:stackitcloud/stackit-cli" = "0.61.0" \ No newline at end of file +"github:stackitcloud/stackit-cli" = "0.61.0" + +[env] +STACKIT_SERVICE_ACCOUNT_KEY_PATH = "{{env.HOME}}/.stackit/credentials.json" \ No newline at end of file From d43370c44a57191523896de7ae64071a899be29e Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Tue, 5 May 2026 15:58:46 +0200 Subject: [PATCH 24/30] chore: update getting started guide with service account key path hint and tfvars file reference --- docs/getting-started.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 2af33e6..234e6e8 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -82,6 +82,9 @@ Create a service account key and configure it for the STACKIT Terraform provider ```bash mkdir -p ~/.stackit stackit service-account key create --email bootstrap-sa-ap82bsi8@sa.stackit.cloud --project-id -y --verbosity error > ~/.stackit/credentials.json + +# Hint: ~ doesnt work for referencing the home folder; if using mise you can ommit this +export STACKIT_SERVICE_ACCOUNT_KEY_PATH=/home//.stackit/credentials.json ``` Refer to the [STACKIT Terraform provider documentation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs) for all supported authentication methods. @@ -117,7 +120,7 @@ tofu init tofu apply ``` -Hint: if you didnt suffix your tfvars file with .auto.tfvars run `tofu apply -var-file ./config` +Hint: if you didnt suffix your tfvars file with .auto.tfvars run `tofu apply -var-file ./config/.tfvars` Review the plan and confirm with `yes`. From 3ed09645d5dbfdb15d8a8c4d8c67d6216af3b3c3 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Wed, 6 May 2026 10:45:42 +0200 Subject: [PATCH 25/30] chore: update configuration files and documentation for improved clarity and consistency Co-authored-by: Copilot --- docs/getting-started.md | 51 ++++++++++++--------- src/config/hub-and-spoke-firewall.tfvars | 12 ++--- src/config/hub-and-spoke.tfvars | 4 ++ src/modules/management/3-bucket.tf | 2 +- src/modules/management/4-service-account.tf | 2 +- src/modules/management/5-observability.tf | 2 +- src/modules/management/outputs.tf | 5 ++ src/outputs.tf | 5 ++ 8 files changed, 50 insertions(+), 33 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 234e6e8..e5d856f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,8 +2,6 @@ This guide walks you through deploying the STACKIT Landing Zone from scratch. ---- - ## Prerequisites - A **STACKIT organization** with your user account registered @@ -11,8 +9,6 @@ This guide walks you through deploying the STACKIT Landing Zone from scratch. - **STACKIT CLI** installed ([Installation guide](https://github.com/stackitcloud/stackit-cli/blob/main/INSTALLATION.md)) - **OpenTofu** (>= 1.10) or **Terraform** (>= 1.10) installed ---- - ## Deployment Flavours Three ready-to-use configurations are provided in `src/config/`: @@ -23,7 +19,7 @@ Three ready-to-use configurations are provided in `src/config/`: | **Hub-Spoke** | `hub-and-spoke.tfvars` | Adds a connectivity hub with a network area and DNS zones. Corporate landing zones connect via the network area. | | **Hub-Spoke + Firewall** | `hub-and-spoke-firewall.tfvars` | Full hub-spoke topology with a pfSense firewall appliance on the WAN/LAN boundary. | -Choose the flavour that matches your requirements and adjust the corresponding `.tfvars` file before deployment. At a minimum, update `owner_email`, `organization_id`, `company_name`, and `company_code`. +Choose the flavour that matches your requirements and adjust the corresponding `.tfvars` file before deployment (step 7). At a minimum, update `owner_email`, `organization_id`, `company_name`, and `company_code`. --- @@ -83,10 +79,12 @@ Create a service account key and configure it for the STACKIT Terraform provider mkdir -p ~/.stackit stackit service-account key create --email bootstrap-sa-ap82bsi8@sa.stackit.cloud --project-id -y --verbosity error > ~/.stackit/credentials.json -# Hint: ~ doesnt work for referencing the home folder; if using mise you can ommit this export STACKIT_SERVICE_ACCOUNT_KEY_PATH=/home//.stackit/credentials.json ``` +> [!NOTE] +> `~` does not work for referencing the home folder. If using mise, you can omit the `STACKIT_SERVICE_ACCOUNT_KEY_PATH` export. + Refer to the [STACKIT Terraform provider documentation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs) for all supported authentication methods. ### 7. Configure variables @@ -108,8 +106,6 @@ Update the values to match your organization. Required variables: ### 8. Initialize OpenTofu/Terraform -Comment out or remove the `backend "s3"` block in `backend.tf` for the initial run (state will be stored locally): - ```bash tofu init ``` @@ -120,24 +116,25 @@ tofu init tofu apply ``` -Hint: if you didnt suffix your tfvars file with .auto.tfvars run `tofu apply -var-file ./config/.tfvars` - Review the plan and confirm with `yes`. +> [!NOTE] +> If you did not copy your tfvars file with the `.auto.tfvars` suffix, pass it explicitly: `tofu apply -var-file ./config/.tfvars` + --- ## Migrating State to the Created Backend -After the first successful apply, the management module has created an S3 bucket for remote state and a service account for ongoing automation. Migrate to this backend to enable team collaboration and state locking. +After the first successful apply, the management module has created an S3 bucket for remote state and a service account for ongoing automation. Migrate to this backend to enable team collaboration. ### 10. Enable the S3 backend -Uncomment the `backend "s3"` block in `backend.tf` and update the `bucket` name to match the Terraform output (format: `-pltfm-mgmt-prod-tfstate`). Configure the bucket credentials from the Secrets Manager in the management project: +Uncomment the `backend "s3"` block in `backend.tf` and update the `bucket` name to match the Terraform output `management_bucket_name_tfstate`: ```hcl terraform { backend "s3" { - bucket = "-pltfm-mgmt-prod-tfstate" + bucket = "" endpoints = { s3 = "https://object.storage.eu01.onstackit.cloud" } @@ -150,14 +147,18 @@ terraform { } } ``` +In the STACKIT Portal, navigate to the management project โ†’ Secrets Manager โ†’ Secrets. Open the secret prefixed with `object_storage_credentials_` and copy the `ACCESS_KEY` and `SECRET_ACCESS_KEY` values. -Set the S3 backend credentials (retrieved from the Secrets Manager instance in the management project): +Set the S3 backend credentials: ```bash -export AWS_ACCESS_KEY_ID= -export AWS_SECRET_ACCESS_KEY= +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= ``` +> [!IMPORTANT] +> These values need to be persisted across terminal sessions. + ### 11. Migrate state ```bash @@ -168,32 +169,38 @@ Confirm the migration when prompted. ### 12. Switch to the management service account -Replace the bootstrap credentials with the service account created by the management module. The service account email is available in the Terraform outputs. Retrieve its credentials from the Secrets Manager instance in the management project. +Replace the bootstrap credentials with the service account created by the management module. -Update the environment variables: +In the STACKIT Portal, navigate to the management project โ†’ Secrets Manager โ†’ Secrets. Open the secret prefixed with `service_account_key_` and copy its value into `~/.stackit/credentials.json`: ```bash -export STACKIT_SERVICE_ACCOUNT_EMAIL= -export STACKIT_SERVICE_ACCOUNT_TOKEN= +cat > ~/.stackit/credentials.json << 'EOF' + +EOF ``` +The `STACKIT_SERVICE_ACCOUNT_KEY_PATH` environment variable already points to this file from step 6, so no further changes are needed. + ### 13. Verify the migration Run a plan to confirm no changes are detected: ```bash -tofu plan -var-file=config/.tfvars +tofu plan ``` The output should show `No changes. Your infrastructure matches the configuration.` +> [!NOTE] +> If you did not copy your tfvars file with the `.auto.tfvars` suffix, pass it explicitly: `tofu plan -var-file ./config/.tfvars` + --- ## Cleanup ### 14. Delete the bootstrap project -The temporary bootstrap project is no longer needed: +The temporary bootstrap project with the service account is no longer needed: ```bash stackit project delete diff --git a/src/config/hub-and-spoke-firewall.tfvars b/src/config/hub-and-spoke-firewall.tfvars index fc6356b..84464cc 100644 --- a/src/config/hub-and-spoke-firewall.tfvars +++ b/src/config/hub-and-spoke-firewall.tfvars @@ -31,6 +31,10 @@ labels = { # "auditor@example.com" # ] +# observability = { +# plan_name = "Observability-Starter-EU01" +# } + ################## ## CONNECTIVITY ## ################## @@ -71,14 +75,6 @@ connectivity = { # allowed_network_ranges = ["0.0.0.0/0"] # } -##################### -## MANAGEMENT ## -##################### - -observability = { - plan_name = "Observability-Starter-EU01" -} - ############### ## SANDBOXES ## ############### diff --git a/src/config/hub-and-spoke.tfvars b/src/config/hub-and-spoke.tfvars index cf27e8e..33a46fb 100644 --- a/src/config/hub-and-spoke.tfvars +++ b/src/config/hub-and-spoke.tfvars @@ -31,6 +31,10 @@ labels = { # "auditor@example.com" # ] +# observability = { +# plan_name = "Observability-Starter-EU01" +# } + ################## ## CONNECTIVITY ## ################## diff --git a/src/modules/management/3-bucket.tf b/src/modules/management/3-bucket.tf index b9117c5..c0a731e 100644 --- a/src/modules/management/3-bucket.tf +++ b/src/modules/management/3-bucket.tf @@ -33,7 +33,7 @@ resource "stackit_objectstorage_credential" "this" { resource "vault_kv_secret_v2" "object_storage_credentials" { mount = stackit_secretsmanager_instance.this.instance_id - name = "service_account_key_${stackit_service_account.automation.name}" + name = "object_storage_credentials_${replace(var.naming_pattern, "-", "_")}" cas = 1 delete_all_versions = true data_json = jsonencode( diff --git a/src/modules/management/4-service-account.tf b/src/modules/management/4-service-account.tf index 24c3f77..96d66b8 100644 --- a/src/modules/management/4-service-account.tf +++ b/src/modules/management/4-service-account.tf @@ -29,7 +29,7 @@ resource "stackit_authorization_organization_role_assignment" "sa_owner" { resource "vault_kv_secret_v2" "service_account_key_automation" { mount = stackit_secretsmanager_instance.this.instance_id - name = "service_account_key_${stackit_service_account.automation.name}" + name = "service_account_key_${replace(var.naming_pattern, "-", "_")}" cas = 1 delete_all_versions = true data_json = stackit_service_account_key.automation.json diff --git a/src/modules/management/5-observability.tf b/src/modules/management/5-observability.tf index c35c8e9..9e54ebc 100644 --- a/src/modules/management/5-observability.tf +++ b/src/modules/management/5-observability.tf @@ -28,7 +28,7 @@ resource "vault_kv_secret_v2" "observability" { count = var.observability != null ? 1 : 0 mount = stackit_secretsmanager_instance.this.instance_id - name = "service_account_key_${stackit_service_account.automation.name}" + name = "observability_credentials_${replace(var.naming_pattern, "-", "_")}" cas = 1 delete_all_versions = true data_json = jsonencode( diff --git a/src/modules/management/outputs.tf b/src/modules/management/outputs.tf index 7946dda..345a4c1 100644 --- a/src/modules/management/outputs.tf +++ b/src/modules/management/outputs.tf @@ -27,4 +27,9 @@ output "secretsmanager_password" { description = "The password of the default Secrets Manager user." value = stackit_secretsmanager_user.default.password sensitive = true +} + +output "bucket_name_tfstate" { + description = "The name of the tfstate object storage bucket." + value = stackit_objectstorage_bucket.tfstate.name } \ No newline at end of file diff --git a/src/outputs.tf b/src/outputs.tf index da4ad27..647d98e 100644 --- a/src/outputs.tf +++ b/src/outputs.tf @@ -17,6 +17,11 @@ output "management_project_id" { value = module.management.project_id } +output "management_bucket_name_tfstate" { + description = "The name of the Management tfstate object storage bucket." + value = module.management.bucket_name_tfstate +} + output "connectivity_network_area_id" { description = "The network area ID created by the regional module." value = try(module.connectivity[0].network_area_id, null) From 824490dfae6284836d2ca63270b45199c83c3bb6 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Wed, 6 May 2026 10:59:15 +0200 Subject: [PATCH 26/30] chore: enhance getting started guide with notes on scaling and service account configuration Co-authored-by: Copilot --- docs/getting-started.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index e5d856f..33ada97 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -21,6 +21,9 @@ Three ready-to-use configurations are provided in `src/config/`: Choose the flavour that matches your requirements and adjust the corresponding `.tfvars` file before deployment (step 7). At a minimum, update `owner_email`, `organization_id`, `company_name`, and `company_code`. +> [!NOTE] +> This single-root-module approach works well for smaller environments. At larger scale โ€” typically beyond 10 landing zones โ€” you may encounter STACKIT API rate limits during applies and slower plan/refresh cycles due to a growing state file. Tools like [Terragrunt](https://terragrunt.gruntwork.io/) can help by splitting landing zones into isolated state files and orchestrating root module calls with proper concurrency controls. If you are planning a larger enterprise deployment, reach out to [STACKIT](https://stackit.de) or a partner offering a verified landing zone solution via the [STACKIT Marketplace](https://marketplace.stackit.cloud). + --- ## Step-by-Step Deployment @@ -77,11 +80,14 @@ Create a service account key and configure it for the STACKIT Terraform provider ```bash mkdir -p ~/.stackit -stackit service-account key create --email bootstrap-sa-ap82bsi8@sa.stackit.cloud --project-id -y --verbosity error > ~/.stackit/credentials.json +stackit service-account key create --email --project-id -y --verbosity error > ~/.stackit/credentials.json export STACKIT_SERVICE_ACCOUNT_KEY_PATH=/home//.stackit/credentials.json ``` +> [!IMPORTANT] +> `STACKIT_SERVICE_ACCOUNT_KEY_PATH` needs to be persisted across terminal sessions. + > [!NOTE] > `~` does not work for referencing the home folder. If using mise, you can omit the `STACKIT_SERVICE_ACCOUNT_KEY_PATH` export. @@ -171,15 +177,10 @@ Confirm the migration when prompted. Replace the bootstrap credentials with the service account created by the management module. -In the STACKIT Portal, navigate to the management project โ†’ Secrets Manager โ†’ Secrets. Open the secret prefixed with `service_account_key_` and copy its value into `~/.stackit/credentials.json`: +In the STACKIT Portal, navigate to the management project โ†’ Secrets Manager โ†’ Secrets. Open the secret prefixed with `service_account_key_`, copy its value and save it to `/home//.stackit/credentials.json`, overwriting the bootstrap credentials. -```bash -cat > ~/.stackit/credentials.json << 'EOF' - -EOF -``` - -The `STACKIT_SERVICE_ACCOUNT_KEY_PATH` environment variable already points to this file from step 6, so no further changes are needed. +> [!NOTE] +> Use the absolute path โ€” `~` does not work here. ### 13. Verify the migration From 21d54b8fae82f067bf3783933301d3dedaddaf06 Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Wed, 6 May 2026 11:15:47 +0200 Subject: [PATCH 27/30] chore: update getting started guide with note on Resource Manager folder deletion and adjust destroy duration in network area configuration --- docs/getting-started.md | 5 ++++- src/modules/connectivity/1-network-area.tf | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 33ada97..cfc97ef 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,6 +2,9 @@ This guide walks you through deploying the STACKIT Landing Zone from scratch. +> [!NOTE] +> Resource Manager folders can only be deleted 7 days after the last project within them has been removed. As a result, applying and destroying everything in a single run is not possible. + ## Prerequisites - A **STACKIT organization** with your user account registered @@ -204,7 +207,7 @@ The output should show `No changes. Your infrastructure matches the configuratio The temporary bootstrap project with the service account is no longer needed: ```bash -stackit project delete +stackit project delete --project-id ``` --- diff --git a/src/modules/connectivity/1-network-area.tf b/src/modules/connectivity/1-network-area.tf index dc35915..1d28dea 100644 --- a/src/modules/connectivity/1-network-area.tf +++ b/src/modules/connectivity/1-network-area.tf @@ -25,7 +25,7 @@ resource "stackit_network_area_region" "this" { # This gives STACKIT time to de-register projects that were attached to the network area # Error: Network area ready for deletion waiting: found non-GenericOpenApiError: network area with id ... has still active projects resource "time_sleep" "wait_before_network_area_region_destroy" { - destroy_duration = "120s" + destroy_duration = "180s" depends_on = [stackit_network_area_region.this] } From 482aec2ea6d22e3fa5e872aa44a223f66c92178a Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Wed, 6 May 2026 11:24:05 +0200 Subject: [PATCH 28/30] chore: update dependabot configuration to include additional directories for Terraform updates Co-authored-by: Copilot --- .github/dependabot.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 351a6bf..e88517a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,7 +2,9 @@ version: 2 updates: - package-ecosystem: "terraform" directories: - - "/modules/*" + - "/src" + - "/src/modules/*" + - "/stackit-verified-modules/*" schedule: interval: "weekly" day: "sunday" From 13523feec6386a493d686ce82e1242d8779b30ae Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Wed, 6 May 2026 11:42:58 +0200 Subject: [PATCH 29/30] chore: update getting started guide with detailed notes on Resource Manager folder deletion and scaling considerations Co-authored-by: Copilot --- docs/getting-started.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index cfc97ef..e9e3ef3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,9 +2,6 @@ This guide walks you through deploying the STACKIT Landing Zone from scratch. -> [!NOTE] -> Resource Manager folders can only be deleted 7 days after the last project within them has been removed. As a result, applying and destroying everything in a single run is not possible. - ## Prerequisites - A **STACKIT organization** with your user account registered @@ -12,6 +9,9 @@ This guide walks you through deploying the STACKIT Landing Zone from scratch. - **STACKIT CLI** installed ([Installation guide](https://github.com/stackitcloud/stackit-cli/blob/main/INSTALLATION.md)) - **OpenTofu** (>= 1.10) or **Terraform** (>= 1.10) installed +> [!NOTE] +> This guide uses `tofu` commands throughout. If you are using Terraform, replace `tofu` with `terraform` โ€” all commands work identically. + ## Deployment Flavours Three ready-to-use configurations are provided in `src/config/`: @@ -25,7 +25,7 @@ Three ready-to-use configurations are provided in `src/config/`: Choose the flavour that matches your requirements and adjust the corresponding `.tfvars` file before deployment (step 7). At a minimum, update `owner_email`, `organization_id`, `company_name`, and `company_code`. > [!NOTE] -> This single-root-module approach works well for smaller environments. At larger scale โ€” typically beyond 10 landing zones โ€” you may encounter STACKIT API rate limits during applies and slower plan/refresh cycles due to a growing state file. Tools like [Terragrunt](https://terragrunt.gruntwork.io/) can help by splitting landing zones into isolated state files and orchestrating root module calls with proper concurrency controls. If you are planning a larger enterprise deployment, reach out to [STACKIT](https://stackit.de) or a partner offering a verified landing zone solution via the [STACKIT Marketplace](https://marketplace.stackit.cloud). +> This single-root-module approach works well for smaller environments. At larger scale โ€” typically beyond 10 landing zones โ€” you may encounter STACKIT API rate limits during applies and slower plan/refresh cycles due to a growing state file. Tools like [Terragrunt](https://terragrunt.gruntwork.io/), [Terramate](https://terramate.io/), or [Spacelift](https://spacelift.io/) can help by splitting landing zones into isolated state files and orchestrating root module calls with proper concurrency controls. If you are planning a larger enterprise deployment, reach out to [STACKIT](https://stackit.de) or a partner offering a verified landing zone solution via the [STACKIT Marketplace](https://marketplace.stackit.cloud). --- @@ -210,6 +210,9 @@ The temporary bootstrap project with the service account is no longer needed: stackit project delete --project-id ``` +> [!NOTE] +> Resource Manager folders can only be deleted 7 days after the last project within them has been removed. Running `tofu apply` followed by `tofu destroy` will therefore fail โ€” the destroy will error when attempting to delete the folders while projects are still within their retention period. + --- ## Post-Deployment (Optional) From f8e4b480c298c5e6db9904b89d07aabb357c24ba Mon Sep 17 00:00:00 2001 From: Matthias Hauber Date: Wed, 6 May 2026 11:46:11 +0200 Subject: [PATCH 30/30] chore: remove scripts directory ownership from CODEOWNERS --- CODEOWNERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 477770f..0993d48 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,2 @@ * @dweezl @mahauber @simpe00 -docs/* @lweberru -scripts/* @lweberru \ No newline at end of file +docs/* @lweberru \ No newline at end of file