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" 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/.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/CODEOWNERS b/CODEOWNERS index 7bf8e5c..0993d48 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,2 @@ -* david.wenzel@stackit.cloud @mahauber -docs/* @lweberru -scripts/* @lweberru \ No newline at end of file +* @dweezl @mahauber @simpe00 +docs/* @lweberru \ No newline at end of file diff --git a/README.md b/README.md index 3e832e1..7af5d00 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,22 @@ -# 🚀 STACKIT Landing Zone Accelerator +
+
+STACKIT logo +
+
+
-**Accelerate your STACKIT cloud adoption with production-ready, modular landing zones.** +# Landing Zone Accelerator -[![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.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) ---- - -## ✨ 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 -- **📐 Scalable Templates** — Start small and grow with ready-to-use deployment templates -- **🌐 Multi-Environment** — Seamlessly manage production and non-production workloads -- **⚡ Quick Start** — Get up and running in minutes with sensible defaults - -## 📦 Modules - -| 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 | -| `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 | -| `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 - -# Navigate to a template -cd examples/01-small-scale - -# Fill out the values - -# Initialize and apply -terraform init -terraform plan -terraform apply -``` - -📖 See the [Getting Started Guide](docs/getting-started.md) for detailed instructions. - ## 📚 Documentation - [Getting Started](docs/getting-started.md) -- [Deployment Guide](docs/deployment-guide.md) - -## 🔍 Linting (TFLint) - -The repository includes automated Terraform linting with `tflint` in GitHub Actions. - -- Workflow: `.github/workflows/tflint.yml` -- Config: `.tflint.hcl` - -Run locally: - -```bash -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`) - -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: - -```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 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..e9e3ef3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -0,0 +1,222 @@ +# 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 + +> [!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/`: + +| 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 (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/), [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). + +--- + +## 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 +# 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. + +### 5. Create a bootstrap service account + +```bash +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: +stackit organization member add --role organization.owner --organization-id +``` + +### 6. Configure service account credentials + +Create a service account key and configure it for the STACKIT Terraform provider: + +```bash +mkdir -p ~/.stackit +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. + +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.auto.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 + +```bash +tofu init +``` + +### 9. Deploy the landing zone + +```bash +tofu apply +``` + +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. + +### 10. Enable the S3 backend + +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 = "" + 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 + } +} +``` +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: + +```bash +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +``` + +> [!IMPORTANT] +> These values need to be persisted across terminal sessions. + +### 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. + +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. + +> [!NOTE] +> Use the absolute path — `~` does not work here. + +### 13. Verify the migration + +Run a plan to confirm no changes are detected: + +```bash +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 with the service account is no longer needed: + +```bash +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) + +### 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 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/README.md b/examples/01-standalone/README.md deleted file mode 100644 index 4a690a2..0000000 --- a/examples/01-standalone/README.md +++ /dev/null @@ -1,52 +0,0 @@ - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | 0.88.0 | - -## Providers - -No providers. - -## Modules - -| 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 | - -## 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 | -| [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 | -| [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 | -| [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 | - -## Outputs - -| Name | Description | -|------|-------------| -| [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/01-standalone/main.tf b/examples/01-standalone/main.tf deleted file mode 100644 index 09d9f04..0000000 --- a/examples/01-standalone/main.tf +++ /dev/null @@ -1,89 +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" - - 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" - - 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 1b8d42d..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 = module.devops.project_id -} - -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 = module.sandboxes.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 04e0844..0000000 --- a/examples/01-standalone/terraform.tfvars +++ /dev/null @@ -1,58 +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" -] - -# Users with admin access to the Platform folder (DevOps, Management) -platform_admins = [ - "platform-admin@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 5432b70..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 "platform_admins" { - type = list(string) - description = "List of platform administrators." - default = [] -} - -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 fd286fa..0000000 --- a/examples/02-hub-spoke/README.md +++ /dev/null @@ -1,65 +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 | -| [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 | - -## 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/main.tf b/examples/02-hub-spoke/main.tf deleted file mode 100644 index eae1e4d..0000000 --- a/examples/02-hub-spoke/main.tf +++ /dev/null @@ -1,129 +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_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 = [] - } - } -} - -################ -## 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 -} - -########################### -## CONNECTIVITY - GLOBAL ## -########################### - -module "connectivity_global" { - source = "../../modules/connectivity-global" - - organization_id = var.organization_id - labels = var.labels - network_areas = var.network_areas -} - -############################# -## CONNECTIVITY - REGIONAL ## -############################# - -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 -} - -############ -## DEVOPS ## -############ - -module "devops" { - source = "../../modules/devops" - - 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" - - 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 = 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 - 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 deleted file mode 100644 index a4e8b41..0000000 --- a/examples/02-hub-spoke/outputs.tf +++ /dev/null @@ -1,48 +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 = module.devops.project_id -} - -output "management_project_id" { - description = "The project ID of the Management project." - 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_project_id" { - description = "The project ID of the regional connectivity project." - value = module.connectivity_regional.project_id -} - -output "connectivity_regional_firewall_public_ip" { - description = "The public IP of the firewall." - value = module.connectivity_regional.firewall_public_ip -} - -output "sandbox_projects" { - description = "The created sandbox projects." - value = module.sandboxes.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 - } - } -} 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 caccdc9..0000000 --- a/examples/02-hub-spoke/terraform.tfvars +++ /dev/null @@ -1,157 +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" - 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" -] - -# 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" - -# 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" - -# Static LAN IP of the pfSense firewall (used as default gateway) -firewall_ip = "10.0.0.220" - -############### -## 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 ## -################## - -# 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 = "data-team@example.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 = "api-team@example.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 abc18e3..0000000 --- a/examples/02-hub-spoke/variables.tf +++ /dev/null @@ -1,144 +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 "platform_admins" { - type = list(string) - description = "List of platform administrators." - default = [] -} - -########################### -## 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" { - type = string - description = "Name key of the network area (from network_areas) to use for the regional connectivity project." -} - -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" -} - -############### -## 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/mise.toml b/mise.toml new file mode 100644 index 0000000..46d8a0b --- /dev/null +++ b/mise.toml @@ -0,0 +1,6 @@ +[tools] +opentofu = "1.11.6" +"github:stackitcloud/stackit-cli" = "0.61.0" + +[env] +STACKIT_SERVICE_ACCOUNT_KEY_PATH = "{{env.HOME}}/.stackit/credentials.json" \ No newline at end of file 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-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/outputs.tf b/modules/connectivity-global/outputs.tf deleted file mode 100644 index 30b15f5..0000000 --- a/modules/connectivity-global/outputs.tf +++ /dev/null @@ -1,4 +0,0 @@ -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 } -} \ 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-global/variables.tf b/modules/connectivity-global/variables.tf deleted file mode 100644 index 3549cdc..0000000 --- a/modules/connectivity-global/variables.tf +++ /dev/null @@ -1,29 +0,0 @@ -variable "labels" { - type = map(string) - description = "Additional labels to apply to all folders." - default = {} -} - -variable "network_area_id" { - type = string - description = "Network Area ID to deploy resources into. Required if network is enabled." - default = null -} - -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), ["1.0.0.1", "1.1.1.1"]) - })) - description = "List of network areas to create, each with its own name, ranges, and configuration." -} - -variable "organization_id" { - type = string - description = "Container ID of the root folder or organization under which the company folder will be created." -} \ No newline at end of file diff --git a/modules/connectivity-regional/3-internal-network.tf b/modules/connectivity-regional/3-internal-network.tf deleted file mode 100644 index 254076d..0000000 --- a/modules/connectivity-regional/3-internal-network.tf +++ /dev/null @@ -1,16 +0,0 @@ -############# -## NETWORK ## -############# - -resource "stackit_network" "lan" { - project_id = stackit_resourcemanager_project.this.project_id - name = "lan_network" - routed = true -} - -resource "stackit_network_interface" "lan" { - name = "vtnet1_lan" - project_id = stackit_resourcemanager_project.this.project_id - network_id = stackit_network.lan.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 deleted file mode 100644 index cc043fc..0000000 --- a/modules/connectivity-regional/4-firewall.tf +++ /dev/null @@ -1,64 +0,0 @@ -##################### -## PFSENSE - 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" { - project_id = stackit_resourcemanager_project.this.project_id - name = "pfsense-2.7.2-amd64-image" - local_file_path = "./pfsense.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" { - project_id = stackit_resourcemanager_project.this.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 - type = "image" - } -} - -############ -## SERVER ## -############ - -# 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 - name = "pfSense" - boot_volume = { - source_type = "volume" - source_id = stackit_volume.pfsense_vol.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 - ] -} \ No newline at end of file diff --git a/modules/connectivity-regional/README.md b/modules/connectivity-regional/README.md deleted file mode 100644 index 8e062df..0000000 --- a/modules/connectivity-regional/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-net-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/modules/connectivity-regional/outputs.tf b/modules/connectivity-regional/outputs.tf deleted file mode 100644 index b27b2ce..0000000 --- a/modules/connectivity-regional/outputs.tf +++ /dev/null @@ -1,24 +0,0 @@ -output "firewall_public_ip" { - description = "The public IP address of the pfSense firewall WAN interface." - value = stackit_public_ip.wan-ip.ip -} - -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 -} - -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 -} 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 e1cf112..0000000 --- a/modules/connectivity-regional/variables.tf +++ /dev/null @@ -1,74 +0,0 @@ -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 folders." - default = {} -} - -variable "naming_pattern" { - type = string - description = "Naming prefix for all resources in this module, e.g. \"myco-pltfm-net-prod\"." -} - -variable "network_area_id" { - type = string - description = "Network Area ID to deploy resources into. Required if network is enabled." -} - -variable "organization_id" { - type = string - description = "Organization ID, required for network area route configuration." -} - -variable "owner_email" { - type = string - description = "Email address of the owner for the folders. 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." - default = null -} - -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 "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/landing-zone/4-secrets-manager.tf b/modules/landing-zone/4-secrets-manager.tf deleted file mode 100644 index b69242d..0000000 --- a/modules/landing-zone/4-secrets-manager.tf +++ /dev/null @@ -1,17 +0,0 @@ -##################### -## SECRETS MANAGER ## -##################### - -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 diff --git a/modules/landing-zone/outputs.tf b/modules/landing-zone/outputs.tf deleted file mode 100644 index d88bbcb..0000000 --- a/modules/landing-zone/outputs.tf +++ /dev/null @@ -1,14 +0,0 @@ -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 -} \ No newline at end of file diff --git a/modules/management/5-observability.tf b/modules/management/5-observability.tf deleted file mode 100644 index 9ab399f..0000000 --- a/modules/management/5-observability.tf +++ /dev/null @@ -1,34 +0,0 @@ -################### -## 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_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" -# } - -# 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 diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..2e26940 --- /dev/null +++ b/src/README.md @@ -0,0 +1,61 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.10 | +| [stackit](#requirement\_stackit) | 0.93.0 | +| [time](#requirement\_time) | 0.13.1 | +| [vault](#requirement\_vault) | 5.7.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [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 + +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](#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 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 + +| 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. | +| [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/src/backend.tf b/src/backend.tf new file mode 100644 index 0000000..c9266ff --- /dev/null +++ b/src/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/src/config/hub-and-spoke-firewall.tfvars b/src/config/hub-and-spoke-firewall.tfvars new file mode 100644 index 0000000..84464cc --- /dev/null +++ b/src/config/hub-and-spoke-firewall.tfvars @@ -0,0 +1,121 @@ +###################### +## 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" +# ] + +# observability = { +# plan_name = "Observability-Starter-EU01" +# } + +################## +## 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" + name = "pfsense-2.7.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..33a46fb --- /dev/null +++ b/src/config/hub-and-spoke.tfvars @@ -0,0 +1,112 @@ +###################### +## 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" +# ] + +# observability = { +# plan_name = "Observability-Starter-EU01" +# } + +################## +## 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..71c99a1 --- /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/src/main.tf b/src/main.tf new file mode 100644 index 0000000..8469371 --- /dev/null +++ b/src/main.tf @@ -0,0 +1,100 @@ +################ +## 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 = var.rm_folders +} + +################ +## 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 + observability = var.observability +} + +################## +## CONNECTIVITY ## +################## + +module "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.connectivity.dns_zones + network_area = var.connectivity.network_area + firewall = var.connectivity.firewall +} + +############ +## DEVOPS ## +############ + +module "devops" { + 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 +} + +############### +## 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 = 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 = 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.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/src/modules/connectivity/1-network-area.tf b/src/modules/connectivity/1-network-area.tf new file mode 100644 index 0000000..1d28dea --- /dev/null +++ b/src/modules/connectivity/1-network-area.tf @@ -0,0 +1,31 @@ +################## +## 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 + } +} + +# 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 = "180s" + + depends_on = [stackit_network_area_region.this] +} diff --git a/modules/connectivity-regional/1-project.tf b/src/modules/connectivity/2-project.tf similarity index 73% rename from modules/connectivity-regional/1-project.tf rename to src/modules/connectivity/2-project.tf index d16e004..c656edd 100644 --- a/modules/connectivity-regional/1-project.tf +++ b/src/modules/connectivity/2-project.tf @@ -4,16 +4,17 @@ locals { project_labels = merge( - var.network_area_id != null ? { "networkArea" = var.network_area_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 } 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 = local.labels } resource "stackit_authorization_project_role_assignment" "this" { diff --git a/modules/connectivity-regional/2-external-network.tf b/src/modules/connectivity/3-external-network.tf similarity index 59% rename from modules/connectivity-regional/2-external-network.tf rename to src/modules/connectivity/3-external-network.tf index 08a92fa..d0bd8ef 100644 --- a/modules/connectivity-regional/2-external-network.tf +++ b/src/modules/connectivity/3-external-network.tf @@ -2,16 +2,24 @@ ## 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 = var.network_area_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" { 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 +37,28 @@ resource "stackit_routing_table_route" "wan" { ############# resource "stackit_network" "wan" { + count = var.firewall != null ? 1 : 0 + project_id = stackit_resourcemanager_project.this.project_id name = "wan_network" + ipv4_prefix = var.firewall.wan_network_range routing_table_id = stackit_routing_table.wan.routing_table_id routed = true } resource "stackit_network_interface" "wan" { + count = var.firewall != null ? 1 : 0 + name = "vtnet0_wan" project_id = stackit_resourcemanager_project.this.project_id - network_id = stackit_network.wan.network_id + network_id = stackit_network.wan[0].network_id + ipv4 = coalesce(var.firewall.wan_ip, cidrhost(var.firewall.wan_network_range, 4)) security = false } resource "stackit_public_ip" "wan-ip" { + count = var.firewall != null ? 1 : 0 + project_id = stackit_resourcemanager_project.this.project_id - network_interface_id = stackit_network_interface.wan.network_interface_id + network_interface_id = stackit_network_interface.wan[0].network_interface_id } \ No newline at end of file diff --git a/src/modules/connectivity/4-internal-network.tf b/src/modules/connectivity/4-internal-network.tf new file mode 100644 index 0000000..18718a7 --- /dev/null +++ b/src/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_network_range + 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 = coalesce(var.firewall.lan_ip, cidrhost(var.firewall.lan_network_range, 4)) + security = false +} \ No newline at end of file diff --git a/src/modules/connectivity/5-firewall.tf b/src/modules/connectivity/5-firewall.tf new file mode 100644 index 0000000..873467d --- /dev/null +++ b/src/modules/connectivity/5-firewall.tf @@ -0,0 +1,58 @@ +########### +## IMAGE ## +########### + +resource "stackit_image" "firewall" { + count = var.firewall != null ? 1 : 0 + + project_id = stackit_resourcemanager_project.this.project_id + name = var.firewall.name + local_file_path = "./firewall-image.qcow2" + disk_format = "qcow2" + min_disk_size = 10 + min_ram = 2 + config = { + uefi = false + } +} + +############ +## VOLUME ## +############ + +resource "stackit_volume" "firewall" { + count = var.firewall != null ? 1 : 0 + + project_id = stackit_resourcemanager_project.this.project_id + name = var.firewall.name + availability_zone = var.firewall.zone + size = var.firewall.volume_size + performance_class = var.firewall.volume_performance_class + source = { + id = stackit_image.firewall[0].image_id + type = "image" + } +} + +############ +## SERVER ## +############ + +# after rollout: https://docs.stackit.cloud/products/quick-deployments/pfsense-firewall/tutorials/configure-pfsense/ +resource "stackit_server" "firewall" { + count = var.firewall != null ? 1 : 0 + + project_id = stackit_resourcemanager_project.this.project_id + name = var.firewall.name + boot_volume = { + source_type = "volume" + source_id = stackit_volume.firewall[0].volume_id + } + availability_zone = var.firewall.zone + machine_type = var.firewall.flavor + + network_interfaces = [ + 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/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 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/connectivity/outputs.tf b/src/modules/connectivity/outputs.tf new file mode 100644 index 0000000..025db01 --- /dev/null +++ b/src/modules/connectivity/outputs.tf @@ -0,0 +1,39 @@ +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 (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 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 +} + +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 +} diff --git a/modules/governance/terraform.tf b/src/modules/connectivity/terraform.tf similarity index 66% rename from modules/governance/terraform.tf rename to src/modules/connectivity/terraform.tf index 851721e..7932297 100644 --- a/modules/governance/terraform.tf +++ b/src/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/src/modules/connectivity/variables.tf b/src/modules/connectivity/variables.tf new file mode 100644 index 0000000..15ba03b --- /dev/null +++ b/src/modules/connectivity/variables.tf @@ -0,0 +1,93 @@ +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 + 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 = "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 { + 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." + default = {} +} + +variable "naming_pattern" { + type = string + 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 = "Organization ID, required for network area and route configuration." +} + +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 "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 = [] +} \ No newline at end of file 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 95% rename from modules/devops/README.md rename to src/modules/devops/README.md index c2deca5..6577001 100644 --- a/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-net-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/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/connectivity-regional/terraform.tf b/src/modules/devops/terraform.tf similarity index 100% rename from modules/connectivity-regional/terraform.tf rename to src/modules/devops/terraform.tf diff --git a/modules/devops/variables.tf b/src/modules/devops/variables.tf similarity index 98% rename from modules/devops/variables.tf rename to src/modules/devops/variables.tf index 631f67f..87deb26 100644 --- a/modules/devops/variables.tf +++ b/src/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/governance/1-rm-folders.tf b/src/modules/governance/1-rm-folders.tf similarity index 90% rename from modules/governance/1-rm-folders.tf rename to src/modules/governance/1-rm-folders.tf index 3e5f758..df3800e 100644 --- a/modules/governance/1-rm-folders.tf +++ b/src/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/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 95% rename from modules/governance/README.md rename to src/modules/governance/README.md index 537d4a2..2bfd363 100644 --- a/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/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 65% rename from modules/landing-zone/3-network.tf rename to src/modules/landing-zone/3-network.tf index d4d05da..57ff21e 100644 --- a/modules/landing-zone/3-network.tf +++ b/src/modules/landing-zone/3-network.tf @@ -2,7 +2,8 @@ ## ROUTING ## ############# resource "stackit_routing_table" "this" { - count = var.network_area_id != null ? 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.network_area_id != null ? 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.network_area_id != null ? 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/src/modules/landing-zone/4-secrets-manager.tf b/src/modules/landing-zone/4-secrets-manager.tf new file mode 100644 index 0000000..ea454bf --- /dev/null +++ b/src/modules/landing-zone/4-secrets-manager.tf @@ -0,0 +1,9 @@ +##################### +## SECRETS MANAGER ## +##################### + +resource "stackit_secretsmanager_instance" "this" { + project_id = stackit_resourcemanager_project.this.project_id + name = "${var.naming_pattern}-default" + acls = length(var.secretsmanager_acls) > 0 ? var.secretsmanager_acls : null +} \ No newline at end of file diff --git a/modules/landing-zone/5-bucket.tf b/src/modules/landing-zone/5-bucket.tf similarity index 67% rename from modules/landing-zone/5-bucket.tf rename to src/modules/landing-zone/5-bucket.tf index 5f9733e..1cc34f3 100644 --- a/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/modules/landing-zone/6-service-account.tf b/src/modules/landing-zone/6-service-account.tf similarity index 63% rename from modules/landing-zone/6-service-account.tf rename to src/modules/landing-zone/6-service-account.tf index 07fbddb..58e2aef 100644 --- a/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/modules/landing-zone/7-dns-zone.tf b/src/modules/landing-zone/7-dns-zone.tf new file mode 100644 index 0000000..f5c6bbe --- /dev/null +++ b/src/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/README.md b/src/modules/landing-zone/README.md similarity index 67% rename from modules/landing-zone/README.md rename to src/modules/landing-zone/README.md index 21d0012..a340f21 100644 --- a/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 | -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-net-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 | +| [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 | -| [network\_area\_id](#input\_network\_area\_id) | Network Area ID to deploy resources into. Required if network is enabled. | `string` | `null` | 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 | | [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/landing-zone/outputs.tf b/src/modules/landing-zone/outputs.tf new file mode 100644 index 0000000..64741fc --- /dev/null +++ b/src/modules/landing-zone/outputs.tf @@ -0,0 +1,34 @@ +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_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 +} + +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 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 73% rename from modules/landing-zone/variables.tf rename to src/modules/landing-zone/variables.tf index 30d04df..797f6e3 100644 --- a/modules/landing-zone/variables.tf +++ b/src/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." @@ -70,4 +76,16 @@ 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 +} + +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/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 93% rename from modules/management/3-bucket.tf rename to src/modules/management/3-bucket.tf index b9117c5..c0a731e 100644 --- a/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/modules/management/4-service-account.tf b/src/modules/management/4-service-account.tf similarity index 92% rename from modules/management/4-service-account.tf rename to src/modules/management/4-service-account.tf index 24c3f77..96d66b8 100644 --- a/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 new file mode 100644 index 0000000..9e54ebc --- /dev/null +++ b/src/modules/management/5-observability.tf @@ -0,0 +1,40 @@ +################### +## OBSERVABILITY ## +################### + +resource "stackit_observability_instance" "this" { + count = var.observability != null ? 1 : 0 + + 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 "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 = "observability_credentials_${replace(var.naming_pattern, "-", "_")}" + 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/modules/management/README.md b/src/modules/management/README.md similarity index 67% rename from modules/management/README.md rename to src/modules/management/README.md index c2094cd..5e9411d 100644 --- a/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 | |------|-------------|------|---------|:--------:| -| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for all resources in this module, e.g. "myco-pltfm-net-prod". | `string` | n/a | yes | +| [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/modules/management/outputs.tf b/src/modules/management/outputs.tf similarity index 86% rename from modules/management/outputs.tf rename to src/modules/management/outputs.tf index 7946dda..345a4c1 100644 --- a/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/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 60% rename from modules/management/variables.tf rename to src/modules/management/variables.tf index 2fd3874..e536fd2 100644 --- a/modules/management/variables.tf +++ b/src/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" { @@ -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/modules/sandboxes/README.md b/src/modules/sandboxes/README.md similarity index 77% rename from modules/sandboxes/README.md rename to src/modules/sandboxes/README.md index aca42a5..9f87852 100644 --- a/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-net-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 | |------|-------------| 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/devops/terraform.tf b/src/modules/sandboxes/terraform.tf similarity index 100% rename from modules/devops/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/src/outputs.tf b/src/outputs.tf new file mode 100644 index 0000000..647d98e --- /dev/null +++ b/src/outputs.tf @@ -0,0 +1,56 @@ +############# +## 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 "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) +} + +output "connectivity_project_id" { + description = "The project ID of the connectivity project." + value = try(module.connectivity[0].project_id, null) +} + +output "connectivity_firewall_public_ip" { + description = "The public IP of the firewall." + value = try(module.connectivity[0].firewall_public_ip, null) +} + +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 + 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/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..227857b --- /dev/null +++ b/src/tests/hub_spoke.tftest.hcl @@ -0,0 +1,102 @@ +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." + } +} \ No newline at end of file diff --git a/src/tests/hub_spoke_firewall.tftest.hcl b/src/tests/hub_spoke_firewall.tftest.hcl new file mode 100644 index 0000000..de1a06c --- /dev/null +++ b/src/tests/hub_spoke_firewall.tftest.hcl @@ -0,0 +1,113 @@ +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"] + } + + observability = { + plan_name = "Observability-Starter-EU01" + } + + 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" + name = "pfsense-2.7.2" + } + } + + 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..ae6c2d0 --- /dev/null +++ b/src/variables.tf @@ -0,0 +1,181 @@ +############# +## 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 "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 + 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 3" + owner_emails = [] + reader_emails = [] + } + landing_zones_corporate = { + name = "Landing Zones - Corporate 3" + owner_emails = [] + reader_emails = [] + } + landing_zones_public = { + name = "Landing Zones - Public 3" + owner_emails = [] + reader_emails = [] + } + sandboxes = { + name = "Sandboxes 3" + 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 + 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 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 = {} +}