Skip to content

Commit a7039d7

Browse files
committed
feat(connectivity): enhance network area configuration with STACKIT resolvers and update documentation
1 parent f46913a commit a7039d7

10 files changed

Lines changed: 59 additions & 18 deletions

File tree

src/config/hub-and-spoke-firewall.tfvars

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ connectivity = {
7474
min_prefix_length = 24
7575
max_prefix_length = 28
7676
default_prefix_length = 25
77+
78+
# Resolvers handed to every network in the area. Left out, the STACKIT resolvers of the configured region apply
79+
# default_nameservers = ["192.214.161.53", "213.17.17.17", "188.34.111.111"]
7780
}
7881

7982
# Delete the variable to skip firewall deployment
@@ -147,7 +150,7 @@ connectivity = {
147150
# platform_dns = {
148151
# type = "host"
149152
# description = "Resolvers the landing zones are pointed at"
150-
# content = ["1.1.1.1", "1.0.0.1"]
153+
# content = ["192.214.161.53", "213.17.17.17", "188.34.111.111"] # STACKIT resolvers, eu01
151154
# }
152155
#
153156
# # Example of a domain based allow rule. A host alias accepts FQDNs and OPNsense re-resolves them on a timer
@@ -193,6 +196,7 @@ connectivity = {
193196
# destination_port = "443"
194197
# }
195198
#
199+
# # Block everything else to the web GUI since the firewall image allows all traffic by default
196200
# block-webgui-from-everywhere-else = {
197201
# sequence = 20
198202
# action = "block"

src/config/hub-and-spoke.tfvars

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,14 @@ connectivity = {
7474
min_prefix_length = 24
7575
max_prefix_length = 28
7676
default_prefix_length = 25
77+
78+
# Resolvers handed to every network in the area. Left out, the STACKIT resolvers of the configured region apply
79+
# default_nameservers = ["192.214.161.53", "213.17.17.17", "188.34.111.111"]
7780
}
7881

79-
# Optional: site-to-site IPsec VPN terminating in the hub, bridging the network area to
80-
# on-premises or another cloud. Uncomment to enable — it provisions a billed VPN gateway.
81-
#
82-
# Roll out in two steps, because each side needs the other's public IP:
83-
# 1. Apply with connections = {} to provision the gateway.
84-
# 2. Read `tofu output connectivity_vpn_public_ips` and configure the remote peer.
85-
# 3. Fill in remote_address below and apply again.
82+
# Optional: site-to-site IPsec VPN terminating in the hub. Uncomment to enable
8683
#
87-
# Pre-shared keys are deliberately not stored here. Supply them separately:
84+
# Pre-shared keys need to be supplied separately:
8885
# export TF_VAR_vpn_pre_shared_keys='{"onprem"={"tunnel1"="<20+ chars>","tunnel2"="<20+ chars>"}}'
8986
#
9087
# vpn = {

src/main.tf

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module "connectivity" {
4444
parent_container_id = module.governance.folder_container_ids["platform"]
4545
organization_id = var.organization_id
4646
labels = var.labels
47+
region = var.region
4748
dns_zones = var.connectivity.dns_zones
4849
network_area = var.connectivity.network_area
4950
firewall = var.connectivity.firewall
@@ -150,7 +151,8 @@ module "landing_zone" {
150151
labels = var.labels
151152
role_assignments = each.value.role_assignments
152153
network_prefix_length = each.value.network_prefix_length
153-
custom_roles = each.value.custom_roles
154-
observability = each.value.observability
155-
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
154+
ipv4_nameservers = try(module.connectivity[0].network_area_nameservers, null)
155+
custom_roles = each.value.custom_roles
156+
observability = each.value.observability
157+
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
156158
}

src/modules/connectivity/1-network-area.tf

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
## NETWORK AREA ##
33
##################
44

5+
locals {
6+
# STACKIT runs its own resolvers per region and recommends them over public ones.
7+
# Regions not listed here have to bring their own network_area.default_nameservers.
8+
stackit_regional_nameservers = {
9+
eu01 = ["192.214.161.53", "213.17.17.17", "188.34.111.111"]
10+
eu02 = ["45.137.172.101", "45.137.172.102", "45.137.172.103"]
11+
}
12+
13+
network_area_nameservers = (
14+
var.network_area.default_nameservers != null
15+
? var.network_area.default_nameservers
16+
: lookup(local.stackit_regional_nameservers, var.region, [])
17+
)
18+
}
19+
520
resource "stackit_network_area" "this" {
621
organization_id = var.organization_id
722
name = var.network_area_name != null ? var.network_area_name : var.naming_pattern
@@ -11,14 +26,22 @@ resource "stackit_network_area" "this" {
1126
resource "stackit_network_area_region" "this" {
1227
organization_id = var.organization_id
1328
network_area_id = stackit_network_area.this.network_area_id
29+
region = var.region
1430

1531
ipv4 = {
1632
network_ranges = [for r in var.network_area.ranges : { prefix = r }]
1733
transfer_network = var.network_area.transfer_network
1834
max_prefix_length = var.network_area.max_prefix_length
1935
min_prefix_length = var.network_area.min_prefix_length
2036
default_prefix_length = var.network_area.default_prefix_length
21-
default_nameservers = var.network_area.default_nameservers
37+
default_nameservers = local.network_area_nameservers
38+
}
39+
40+
lifecycle {
41+
precondition {
42+
condition = length(local.network_area_nameservers) > 0
43+
error_message = "No STACKIT resolvers are known for region ${var.region}. Set connectivity.network_area.default_nameservers explicitly."
44+
}
2245
}
2346
}
2447

src/modules/connectivity/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ No modules.
5151
| <a name="input_firewall"></a> [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). | <pre>object({<br/> zone = string<br/> flavor = string<br/> name = string<br/> volume_performance_class = optional(string, "storage_premium_perf4")<br/> volume_size = optional(number, 16)<br/> lan_network_range = string<br/> wan_network_range = string<br/> lan_ip = optional(string, null)<br/> wan_ip = optional(string, null)<br/> })</pre> | `null` | no |
5252
| <a name="input_labels"></a> [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no |
5353
| <a name="input_naming_pattern"></a> [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes |
54-
| <a name="input_network_area"></a> [network\_area](#input\_network\_area) | Network area configuration including IP ranges, transfer network, and prefix length settings. | <pre>object({<br/> ranges = list(string)<br/> transfer_network = string<br/> min_prefix_length = optional(number, 24)<br/> max_prefix_length = optional(number, 28)<br/> default_prefix_length = optional(number, 28)<br/> default_nameservers = optional(list(string), ["1.0.0.1", "1.1.1.1"])<br/> })</pre> | n/a | yes |
54+
| <a name="input_network_area"></a> [network\_area](#input\_network\_area) | Network area configuration including IP ranges, transfer network, and prefix length settings. default\_nameservers falls back to the STACKIT resolvers of var.region when unset. | <pre>object({<br/> ranges = list(string)<br/> transfer_network = string<br/> min_prefix_length = optional(number, 24)<br/> max_prefix_length = optional(number, 28)<br/> default_prefix_length = optional(number, 28)<br/> default_nameservers = optional(list(string), null)<br/> })</pre> | n/a | yes |
5555
| <a name="input_network_area_name"></a> [network\_area\_name](#input\_network\_area\_name) | Name of the network area to create for this region. | `string` | `null` | no |
5656
| <a name="input_organization_id"></a> [organization\_id](#input\_organization\_id) | Organization ID, required for network area and route configuration. | `string` | n/a | yes |
5757
| <a name="input_owner_email"></a> [owner\_email](#input\_owner\_email) | Email address of the owner for the project. Required for STACKIT resource manager. | `string` | n/a | yes |
5858
| <a name="input_parent_container_id"></a> [parent\_container\_id](#input\_parent\_container\_id) | Parent container ID (folder or organization) where the project will be created. | `string` | n/a | yes |
5959
| <a name="input_project_name"></a> [project\_name](#input\_project\_name) | Name of the STACKIT project to create. Falls back to naming\_pattern if not set. | `string` | `null` | no |
60+
| <a name="input_region"></a> [region](#input\_region) | STACKIT region the network area region is created in. Also selects the default resolvers when network\_area.default\_nameservers is unset. | `string` | `"eu01"` | no |
6061
| <a name="input_role_assignments"></a> [role\_assignments](#input\_role\_assignments) | List of role assignments for the project. Subject can be a user email or service account email. | <pre>list(object({<br/> role = string<br/> subject = string<br/> }))</pre> | `[]` | no |
6162
| <a name="input_vpn"></a> [vpn](#input\_vpn) | IPsec VPN gateway for the hub, attached to the network area through the connectivity project. Set to null to skip. The gateway is HA: it terminates two tunnels in separate availability zones, each with its own public IP. Connections are created in a second apply once the remote peer addresses are known. Supports POLICY\_BASED and ROUTE\_BASED routing. | <pre>object({<br/> display_name = optional(string, null)<br/> plan_id = optional(string, "p100")<br/> routing_type = optional(string, "ROUTE_BASED")<br/> availability_zones = object({<br/> tunnel1 = string<br/> tunnel2 = string<br/> })<br/> connections = optional(map(object({<br/> display_name = optional(string, null)<br/> enabled = optional(bool, true)<br/> local_subnets = optional(list(string), null)<br/> remote_subnets = optional(list(string), null)<br/> static_routes = optional(list(string), null)<br/> tunnel1 = object({<br/> remote_address = string<br/> peering = optional(object({<br/> local_address = string<br/> remote_address = string<br/> }), null)<br/> phase1 = optional(object({<br/> encryption_algorithms = optional(list(string), ["aes256"])<br/> integrity_algorithms = optional(list(string), ["sha2_384"])<br/> dh_groups = optional(list(string), ["ecp384"])<br/> rekey_time = optional(number, null)<br/> }), {})<br/> phase2 = optional(object({<br/> encryption_algorithms = optional(list(string), ["aes256"])<br/> integrity_algorithms = optional(list(string), ["sha2_384"])<br/> dh_groups = optional(list(string), ["ecp384"])<br/> rekey_time = optional(number, null)<br/> dpd_action = optional(string, null)<br/> start_action = optional(string, null)<br/> }), {})<br/> })<br/> tunnel2 = object({<br/> remote_address = string<br/> peering = optional(object({<br/> local_address = string<br/> remote_address = string<br/> }), null)<br/> phase1 = optional(object({<br/> encryption_algorithms = optional(list(string), ["aes256"])<br/> integrity_algorithms = optional(list(string), ["sha2_384"])<br/> dh_groups = optional(list(string), ["ecp384"])<br/> rekey_time = optional(number, null)<br/> }), {})<br/> phase2 = optional(object({<br/> encryption_algorithms = optional(list(string), ["aes256"])<br/> integrity_algorithms = optional(list(string), ["sha2_384"])<br/> dh_groups = optional(list(string), ["ecp384"])<br/> rekey_time = optional(number, null)<br/> dpd_action = optional(string, null)<br/> start_action = optional(string, null)<br/> }), {})<br/> })<br/> })), {})<br/> })</pre> | `null` | no |
6263
| <a name="input_vpn_pre_shared_keys"></a> [vpn\_pre\_shared\_keys](#input\_vpn\_pre\_shared\_keys) | Pre-shared keys per VPN connection key, one per tunnel. Kept separate from var.vpn so the connection topology stays committable; supply through TF\_VAR\_vpn\_pre\_shared\_keys or a gitignored tfvars file. Minimum 20 characters. | <pre>map(object({<br/> tunnel1 = string<br/> tunnel2 = string<br/> }))</pre> | `{}` | no |
@@ -70,6 +71,7 @@ No modules.
7071
| <a name="output_firewall_next_hop_ip"></a> [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 LAN IP). |
7172
| <a name="output_firewall_public_ip"></a> [firewall\_public\_ip](#output\_firewall\_public\_ip) | The public IP address of the firewall WAN interface. |
7273
| <a name="output_network_area_id"></a> [network\_area\_id](#output\_network\_area\_id) | The ID of the created network area. |
74+
| <a name="output_network_area_nameservers"></a> [network\_area\_nameservers](#output\_network\_area\_nameservers) | Resolvers configured as the network area default, either from network\_area.default\_nameservers or the STACKIT resolvers of the region. |
7375
| <a name="output_project_container_id"></a> [project\_container\_id](#output\_project\_container\_id) | The container ID of the created STACKIT project. |
7476
| <a name="output_project_id"></a> [project\_id](#output\_project\_id) | The project ID of the created STACKIT project. |
7577
| <a name="output_project_name"></a> [project\_name](#output\_project\_name) | The name of the created STACKIT project. |

src/modules/connectivity/outputs.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ output "network_area_id" {
2323
value = stackit_network_area.this.network_area_id
2424
}
2525

26+
output "network_area_nameservers" {
27+
description = "Resolvers configured as the network area default, either from network_area.default_nameservers or the STACKIT resolvers of the region."
28+
value = local.network_area_nameservers
29+
}
30+
2631
output "project_container_id" {
2732
description = "The container ID of the created STACKIT project."
2833
value = stackit_resourcemanager_project.this.container_id

src/modules/connectivity/variables.tf

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ variable "network_area" {
5151
min_prefix_length = optional(number, 24)
5252
max_prefix_length = optional(number, 28)
5353
default_prefix_length = optional(number, 28)
54-
default_nameservers = optional(list(string), ["1.0.0.1", "1.1.1.1"])
54+
default_nameservers = optional(list(string), null)
5555
})
56-
description = "Network area configuration including IP ranges, transfer network, and prefix length settings."
56+
description = "Network area configuration including IP ranges, transfer network, and prefix length settings. default_nameservers falls back to the STACKIT resolvers of var.region when unset."
5757
}
5858

5959
variable "network_area_name" {
@@ -83,6 +83,12 @@ variable "project_name" {
8383
default = null
8484
}
8585

86+
variable "region" {
87+
type = string
88+
description = "STACKIT region the network area region is created in. Also selects the default resolvers when network_area.default_nameservers is unset."
89+
default = "eu01"
90+
}
91+
8692
variable "role_assignments" {
8793
type = list(object({
8894
role = string

src/modules/landing-zone/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ No modules.
4747
| <a name="input_custom_roles"></a> [custom\_roles](#input\_custom\_roles) | List of custom roles to create for the project. | <pre>list(object({<br/> name = string<br/> description = string<br/> permissions = list(string)<br/> }))</pre> | n/a | yes |
4848
| <a name="input_dns_zone_name"></a> [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 |
4949
| <a name="input_firewall_next_hop_ip"></a> [firewall\_next\_hop\_ip](#input\_firewall\_next\_hop\_ip) | IP address of the firewall next hop. | `string` | `null` | no |
50-
| <a name="input_ipv4_nameservers"></a> [ipv4\_nameservers](#input\_ipv4\_nameservers) | List of IPv4 nameservers for the network. | `list(string)` | `null` | no |
50+
| <a name="input_ipv4_nameservers"></a> [ipv4\_nameservers](#input\_ipv4\_nameservers) | List of IPv4 nameservers for the network. Null takes the network area defaults on creation and leaves an existing network untouched. | `list(string)` | `null` | no |
5151
| <a name="input_labels"></a> [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no |
5252
| <a name="input_naming_pattern"></a> [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-hub-prod". | `string` | n/a | yes |
5353
| <a name="input_network_area_id"></a> [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if corporate is true. | `string` | `null` | no |

src/modules/landing-zone/variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ variable "firewall_next_hop_ip" {
7474

7575
variable "ipv4_nameservers" {
7676
type = list(string)
77-
description = "List of IPv4 nameservers for the network."
77+
description = "List of IPv4 nameservers for the network. Null takes the network area defaults on creation and leaves an existing network untouched."
7878
default = null
7979
}
8080

src/variables.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ variable "connectivity" {
249249
min_prefix_length = optional(number, 24)
250250
max_prefix_length = optional(number, 28)
251251
default_prefix_length = optional(number, 28)
252+
# Unset means the STACKIT resolvers of var.region, see modules/connectivity/1-network-area.tf.
253+
default_nameservers = optional(list(string), null)
252254
}), null)
253255
firewall = optional(object({
254256
zone = string

0 commit comments

Comments
 (0)