Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
* @lweberru @mahauber @simpe00
docs/* @lweberru
* @lweberru @mahauber @simpe00 @dweezl
30 changes: 26 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This repository is a production-ready OpenTofu/Terraform framework for deploying a STACKIT Landing Zone. It provisions the complete cloud foundation, covering governance hierarchy, identity and access management, shared networking, optional firewall, DNS, secrets management, observability, and repeatable per-workload project templates.

Everything is composed from six modules under `src/modules/` and wired together in `src/main.tf`. A single `terraform apply` with one of the three reference configs in `src/config/` stands up the full platform.
Everything is composed from six modules under `src/modules/` and wired together in `src/main.tf`. A single `terraform apply` with one of the complete reference configurations in `src/config/` stands up the full platform.

## Two-Layer Model

Expand Down Expand Up @@ -61,11 +61,11 @@ Source: `src/modules/management/`

### Connectivity

Builds the network hub project (`<company_code>-pltfm-hub-prod`) that all corporate landing zones attach to. This is the most complex module.
Builds one network hub project per configured connectivity domain. Each corporate landing zone attaches explicitly to one domain. This is the most complex module.

#### Network Area

A STACKIT Network Area defines a shared private IP address space at the organization level. All corporate landing zone networks are created inside this area and can reach each other over private IPs without any additional peering.
A STACKIT Network Area (SNA) defines a shared private IP address space at the organization level. Corporate landing zone networks created in the same SNA can reach each other over private IPs without additional peering.

Configuration drives the area's address plan:

Expand All @@ -79,6 +79,28 @@ network_area = {
}
```

#### Multiple Network Areas

`connectivity.network_areas` creates multiple independent connectivity domains. It is a map keyed by stable, meaningful identifiers; keys may represent any business, security, tenant, connectivity, or regional boundary, such as `regulated`, `tenant_a`, `private_connectivity`, `eu01`, or `eu02`. They are not restricted to development and production environments.

Each key creates its own SNA, connectivity project, WAN routing table, and DNS defaults. Corporate landing zones select their domain with `network_area_key`; DNS zones use `dns_zones.<zone>.network_area_key`. Platform Kubernetes uses `platform_kubernetes.<key>.network.network_area_key`. Connectivity projects are labeled with the SNA ID and the corresponding key, and the `*_by_area` outputs use the same keys.

Use multiple SNAs only for a required private network boundary. Suitable reasons include regulatory separation, tenant isolation in a shared company-level STACKIT organization, and business units with independent ownership, address plans, or external connectivity requirements. See [the complete regulated/shared configuration](../src/config/hub-and-spoke-multi-area.tfvars), [the finance/research configuration](../src/config/hub-and-spoke-finance-research.tfvars), and [the tenant-isolation configuration](../src/config/hub-and-spoke-tenant-isolation.tfvars).

#### Shared Services Across SNAs

A STACKIT project can attach to exactly one SNA. A centrally operated private service, such as an internal Git platform, therefore cannot be directly attached to both a `prod` and a `nonprod` SNA. SNAs also have no shared private routing path.

Choose multiple SNAs only when this constraint is intentional. Shared services must be public or SaaS services secured by IAM and network restrictions, be duplicated per SNA, or be connected through explicit external endpoints, such as site-to-site VPN over the internet. The latter adds operational and security responsibilities and must be designed and verified separately. A shared SNA with separate projects, IAM roles, and subnet allocations is usually the simpler choice when workloads need frequent private access to the same platform services.

The legacy `connectivity.network_area` input remains supported for single-area deployments and maps to the `default` key. Existing scalar connectivity outputs continue to reference that legacy default area.

#### Multi-Region Deployments

The root module supports a single-stack topology in `eu01` and `eu02` through static STACKIT provider aliases. Each region receives its own connectivity hub, SNA, landing zones, and optional Platform Kubernetes cluster; select the region explicitly in the configuration. Provider aliases cannot be selected dynamically, so supporting another region requires a corresponding provider alias and regional module instances.

Regional hubs remain isolated by default. Inter-region connectivity requires explicit external VPN endpoints and routes; it is not created by the multi-region scenario. See [the complete multi-region configuration](../src/config/hub-and-spoke-multi-region.tfvars).

#### WAN Routing Table

A routing table named `wan` is created with a single default route:
Expand Down Expand Up @@ -174,7 +196,7 @@ Source: `src/modules/sandboxes/`

## Deployment Flavors

Three reference configurations are provided in `src/config/`. Select the one that matches your network requirements.
Complete reference configurations are provided in `src/config/`. Select the one that matches your network requirements; [Getting Started](getting-started.md#deployment-flavours) lists all available scenarios.

### Standalone

Expand Down
13 changes: 11 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,25 @@ This guide walks you through deploying the STACKIT Landing Zone from scratch.

## Deployment Flavours

Three ready-to-use configurations are provided in `src/config/`:
Eight complete, 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 an OPNsense firewall appliance on the WAN/LAN boundary. |
| **Regulated and Shared** | `hub-and-spoke-multi-area.tfvars` | Separates regulated workloads from shared workloads with different security requirements. |
| **Finance and Research** | `hub-and-spoke-finance-research.tfvars` | Isolates business units with distinct owners, address plans, and connectivity requirements. |
| **Prod/Nonprod + Firewalls** | `hub-and-spoke-prod-nonprod-firewall.tfvars` | Dedicated SNAs and OPNsense appliances for production and non-production. |
| **Tenant Isolation** | `hub-and-spoke-tenant-isolation.tfvars` | Separates three tenants that share one company-level STACKIT organization. |
| **Multi-Region** | `hub-and-spoke-multi-region.tfvars` | Deploys independent connectivity hubs, landing zones, and Platform Kubernetes clusters in `eu01` and `eu02`. |

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`.

The multi-SNA scenario files are complete configurations. Replace the placeholder values for `owner_email`, `company_name`, `company_code`, and `organization_id` before applying them. The area keys are examples only; choose stable keys that match your boundary. See [Multiple Network Areas](architecture.md#multiple-network-areas) for the decision guidance and constraints.

The Prod/Nonprod + Firewalls scenario creates one appliance per SNA. Its OPNsense policies must currently be configured for each appliance after bootstrap; the single `firewall_config` block is not distributed automatically.

The firewall flavour takes one extra step: the appliance boots unconfigured and its policy is pushed in a second apply, from the `firewall_config` block that ships commented out in the same `.tfvars` file. Until then it filters nothing and its web GUI is reachable from the internet — see [Configure OPNsense firewall](#configure-opnsense-firewall). It deploys a single appliance by default, which is the default route of every corporate landing zone and therefore a single point of failure; the commented `connectivity.firewall.ha` block turns it into an active/passive CARP pair — see [Make the firewall highly available](#make-the-firewall-highly-available).

Both hub-spoke flavours can additionally terminate a site-to-site IPsec VPN in the hub. It is disabled by default — see the commented `connectivity.vpn` block in the `.tfvars` file and [Site-to-Site VPN](architecture.md#site-to-site-vpn-optional). If you deploy the firewall flavour, read [what traffic the firewall actually sees](architecture.md#what-goes-through-the-firewall) before relying on it for VPN inspection.
Expand Down Expand Up @@ -106,7 +115,7 @@ Refer to the [STACKIT Terraform provider documentation](https://registry.terrafo

### 7. Configure variables

Copy and edit the `.tfvars` file matching your chosen deployment flavour:
Copy and edit the `.tfvars` file matching your deployment scenario:

```bash
cp config/standalone.tfvars terraform.auto.tfvars
Expand Down
55 changes: 55 additions & 0 deletions src/config/hub-and-spoke-finance-research.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
############################################
## FINANCE AND RESEARCH BUSINESS UNITS ##
############################################

owner_email = "platform@example.com"
company_name = "Example Corp"
company_code = "exc"
organization_id = "00000000-0000-0000-0000-000000000000"
region = "eu01"

# Finance and research have independent owners, address plans, and connectivity
# requirements, but share the same STACKIT organization.
connectivity = {
naming_pattern = "exc-connectivity"

network_areas = {
finance = {
name = "finance-sna"
ranges = ["10.0.0.0/16"]
transfer_network = "10.1.0.0/24"
max_prefix_length = 28
min_prefix_length = 24
default_prefix_length = 26
}
research = {
name = "research-sna"
ranges = ["10.2.0.0/16"]
transfer_network = "10.3.0.0/24"
max_prefix_length = 28
min_prefix_length = 24
default_prefix_length = 26
}
}
}

landing_zones = {
finance = {
project_name = "Finance Workload"
project_code = "finance"
owner_email = "finance-platform@example.com"
env = "prod"
corporate = true
network_area_key = "finance"
network_prefix_length = 24
}
research = {
project_name = "Research Workload"
project_code = "research"
owner_email = "research-platform@example.com"
env = "prod"
corporate = true
network_area_key = "research"
network_prefix_length = 24
}
}
67 changes: 67 additions & 0 deletions src/config/hub-and-spoke-multi-area.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
###########################
## MULTI-AREA CONNECTIVITY ##
###########################

owner_email = "platform@example.com"
company_name = "Example Corp"
company_code = "exc"
organization_id = "00000000-0000-0000-0000-000000000000"
region = "eu01"

# Area keys represent arbitrary business, security, tenant, or regional boundaries.
# This example isolates regulated workloads while less-sensitive shared workloads
# use a second SNA. Landing zones select their SNA with network_area_key.
connectivity = {
naming_pattern = "example-connectivity"

network_areas = {
regulated = {
name = "regulated-sna"
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
}
shared = {
name = "shared-sna"
ranges = ["10.1.0.0/16"]
transfer_network = "10.254.0.0/24"
min_prefix_length = 24
max_prefix_length = 28
default_prefix_length = 25
}
}

dns_zones = {
regulated = {
dns_name = "regulated.example.stackit.run"
network_area_key = "regulated"
}
shared = {
dns_name = "shared.example.stackit.run"
network_area_key = "shared"
}
}
}

landing_zones = {
regulated_workload = {
project_name = "Regulated Workload"
project_code = "regulated"
owner_email = "platform@example.com"
env = "live"
corporate = true
network_area_key = "regulated"
network_prefix_length = 24
}
shared_workload = {
project_name = "Shared Workload"
project_code = "shared"
owner_email = "platform@example.com"
env = "live"
corporate = true
network_area_key = "shared"
network_prefix_length = 24
}
}
106 changes: 106 additions & 0 deletions src/config/hub-and-spoke-multi-region.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
##########################
## MULTI-REGION HUB-SPOKE ##
##########################

owner_email = "platform@example.com"
company_name = "Example Corp"
company_code = "exc"
organization_id = "00000000-0000-0000-0000-000000000000"

# Each enabled region creates an independent connectivity hub through its static
# provider alias. Hubs do not communicate implicitly; configure VPN peers and
# routes explicitly when inter-region traffic is required.
connectivity_regions = {
eu01 = {
naming_pattern = "exc-connectivity-eu01"
network_areas = {
primary = {
name = "eu01-primary-sna"
ranges = ["10.0.0.0/16"]
transfer_network = "10.1.0.0/24"
min_prefix_length = 24
max_prefix_length = 28
default_prefix_length = 25
}
}
}
eu02 = {
naming_pattern = "exc-connectivity-eu02"
network_areas = {
primary = {
name = "eu02-primary-sna"
ranges = ["10.2.0.0/16"]
transfer_network = "10.3.0.0/24"
min_prefix_length = 24
max_prefix_length = 28
default_prefix_length = 25
}
}
}
}

platform_kubernetes = {
eu01 = {
region = "eu01"
network = {
sna_enabled = true
network_area_key = "primary"
}
cluster = {
name = "platform-eu01"
}
}
eu02 = {
region = "eu02"
network = {
sna_enabled = true
network_area_key = "primary"
}
observability = {
enabled = false
}
cluster = {
name = "platform-eu02"
node_pools = [
{
name = "system"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu02-1"]
},
{
name = "application"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu02-2"]
},
]
}
}
}

landing_zones = {
eu01_workload = {
project_name = "EU01 Workload"
project_code = "eu01app"
owner_email = "platform@example.com"
env = "prod"
region = "eu01"
corporate = true
network_area_key = "primary"
network_prefix_length = 24
}
eu02_workload = {
project_name = "EU02 Workload"
project_code = "eu02app"
owner_email = "platform@example.com"
env = "prod"
region = "eu02"
corporate = true
network_area_key = "primary"
network_prefix_length = 24
secretsmanager_enabled = false
}
}
Loading
Loading