diff --git a/.gitignore b/.gitignore index 27a72fa..6fbdb07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,26 @@ -# Local .terraform directories +# OpenTofu / Terraform working files .terraform/ - -# .tfstate files *.tfstate *.tfstate.* - -# Crash log files +.terraform.tfstate.lock.info crash.log crash.*.log +*tfplan* -# Exclude all .tfvars files, which are likely to contain sensitive data, such as -# password, private keys, and other secrets. These should not be part of version -# control as they are data points which are potentially sensitive and subject -# to change depending on the environment. -# *.tfvars -# *.tfvars.json - -# Ignore override files as they are usually used to override resources locally and so -# are not checked in +# Local overrides override.tf override.tf.json *_override.tf *_override.tf.json -# Ignore transient lock info files created by terraform apply -.terraform.tfstate.lock.info - -# Include override files you do wish to add to version control using negated pattern -# !example_override.tf - -# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan -# example: *tfplan* -*tfplan* +# Provider lock file +.terraform.lock.hcl -# Ignore CLI configuration files +# CLI configuration .terraformrc terraform.rc -.terraform.lock.hcl - -# Local AI/model-serving credentials and generated semantic cache +# Local credentials and agent state openai.token .env .env.local @@ -47,7 +28,7 @@ openai.token .agents .claude -# Local persistent E2E runtime variables (canonical for this troubleshooting phase) +# Local E2E runtime variables src/config/e2e.tfvars src/config/e2e-bootstrap.override.tfvars @@ -55,5 +36,8 @@ src/config/e2e-bootstrap.override.tfvars .firewall-api-credentials.json src/.firewall-api-credentials.json -# Ignore QEMU disk images (firewall) +# QEMU disk images (firewall) *.qcow2 + +# macOS +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32e26b5..e487de2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,18 @@ repos: rev: v2.2.2 hooks: - id: tofu_fmt - - id: tofu_docs - args: - - --args=--config=.terraform-docs.yml \ No newline at end of file + + - repo: local + hooks: + - id: terraform-docs + name: terraform-docs + entry: >- + mise exec -- terraform-docs markdown table + --recursive + --recursive-path modules + --output-file README.md + --output-mode inject + src + language: system + pass_filenames: false + files: ^src/.*\.(tf|tofu|terraform\.lock\.hcl)$ diff --git a/CODEOWNERS b/CODEOWNERS index 79f8678..ccbb94a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1 @@ -* @lweberru @mahauber @simpe00 -docs/* @lweberru \ No newline at end of file +* @lweberru @mahauber @simpe00 @dweezl diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..540ed16 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,65 @@ +# Contributing + +Thanks for taking the time to contribute. This document describes the local setup, the conventions this repository follows, and what a pull request is expected to contain. + +## Local setup + +Tool versions are pinned in `mise.toml`. Install them once: + +```bash +mise install +``` + +That provides OpenTofu, the STACKIT CLI, `terraform-docs` and `pre-commit` at the versions this repository is tested against. + +Install the Git hooks: + +```bash +pre-commit install +``` + +The hooks run `tofu fmt` and regenerate the module documentation. If a hook rewrites a file, stage the result and commit again. + +The documentation hook invokes `terraform-docs` through `mise exec`, so it picks up the version pinned in `mise.toml` and also works when Git is started from an editor that does not have the mise environment loaded. + +Deploying against a real STACKIT organization additionally requires a service account key. `mise.toml` expects it at `~/.stackit/credentials.json`; see [docs/getting-started.md](docs/getting-started.md). + +## Before you open a pull request + +```bash +cd src +tofu fmt -recursive +tofu init -backend=false +tofu validate +tofu test +``` + +`tofu test` runs the three flavor test suites in `src/tests/` as plan-only runs. They reach the STACKIT API for validation but create nothing. + +## Conventions + +**Terraform layout.** Every module keeps its `terraform` block in `terraform.tf`, its variables in `variables.tf` and its outputs in `outputs.tf`. Resource files carry a numeric prefix when reading order matters, and each group of resources is introduced by a section header box. + +**Provider versions.** The root module in `src/terraform.tf` pins every provider to an exact version. Child modules under `src/modules/` declare only a minimum with `>=`. Never add an upper bound in a child module; it silently blocks the root from upgrading. + +**Naming.** `snake_case` for every block label, variable, output and local. Resource names do not repeat the resource type. + +**Variables and outputs.** Every variable needs a `type` and a `description`. Every output needs a `description` before its `value`. + +**Comments.** A comment explains a constraint the code cannot show, such as a provider bug or an OPNsense API quirk. Anything that describes what the code does or how the architecture fits together belongs in [docs/architecture.md](docs/architecture.md). + +**Documentation.** Module READMEs are generated. Do not edit the block between the `BEGIN_TF_DOCS` and `END_TF_DOCS` markers by hand; run the pre-commit hook instead. + +**Example configuration.** The tfvars files in `src/config/` are documentation. Keep them on placeholder values such as `owner@example.com` and a zero UUID for the organization. Never commit an identifier from a real environment. + +## Commits and pull requests + +Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/), with the module as the optional scope: + +``` +feat(connectivity): add active/passive CARP support +fix(audit-logs): add HTTPS scheme to URLs +docs: clarify firewall bootstrap steps +``` + +Open the pull request against `main`. Describe what changes for a consumer of the landing zone, and say which flavor you tested against. CI runs formatting, validation, TFLint and the test suites; all of them have to pass before review. diff --git a/README.md b/README.md index 81faf0f..323f1bf 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The STACKIT Landing Zone Accelerator provides a comprehensive Terraform-based fr ## 🤝 Contributing -Contributions are welcome! Please feel free to submit a Pull Request. +Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the local setup, the conventions this repository follows, and what a pull request should contain. ## 📄 License diff --git a/docs/architecture.md b/docs/architecture.md index 61e296d..31c16ca 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -130,7 +130,7 @@ A CARP virtual IP on the LAN — `.6` of `lan_network_range` by default — repl Two things are pushed outside the OPNsense provider, because it only ever talks to the primary: - `modules/connectivity/scripts/configure-ha.sh` writes the node-local half into each appliance during apply: the CARP VIP, `advskew`, the pfsync peer, and the XMLRPC sync target. These are exactly the settings the config sync does not replicate. It authenticates with the appliance login, so it works on the backup, which has no API key and never needs one. -- `modules/firewall-config/scripts/sync-ha-peer.sh` replicates the policy to the backup after every change. OPNsense's XMLRPC sync only fires on GUI saves, never on API writes, so without this the backup runs an empty ruleset and black-holes traffic the moment it becomes CARP master. +- `modules/firewall-config/scripts/sync-ha-peer.sh` replicates the policy to the backup after every change. OPNsense's XMLRPC sync only fires on GUI saves, never on API writes, so without this the backup runs an empty ruleset and black-holes traffic the moment it becomes CARP master. A failover test without the policy sync measured an 86 second outage, against about one second with it. The `fw_cluster` alias and the `allow-fw-carp` / `allow-fw-pfsync` rules that let the two nodes talk are injected into `firewall_config` automatically when HA is on, sequenced at 90 and 91 — ahead of every rule the example policy ships. They are not in the `.tfvars` because a `block-lz-to-lz` rule placed above them silently kills the election and the state sync. @@ -160,6 +160,14 @@ The `corporate` flag is the key switch: Source: `src/modules/landing-zone/` +### Landing Zone on Kubernetes + +Every entry in `landing_zone_namespace_services` gets a tenant slice of the shared platform cluster: its own namespace, a scoped service account with a Role limited to that namespace, and optionally a Kyverno policy that blocks direct Secret management, so credentials have to come through the Secrets Manager. + +The sample workload behind `sample_load` is demo material and not part of the landing zone contract. It deploys a pod, a Gateway API route and a DNS record that together prove the path from the internet to a namespace works. Remove it once real workloads move in. + +Source: `src/_landing-zone-kubernetes.tf`, `src/modules/namespace-service-demo/` + ### DevOps (optional) Provisions a separate DevOps project (`-pltfm-devops-prod`) with a managed Git instance (Gitea or equivalent, controlled by `git_flavor`). Network access can be restricted to specific CIDR ranges via `allowed_network_ranges`. Disabled by default: enable by setting the `devops` variable. diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md index f9bd34b..af9121b 100644 --- a/docs/diagrams/README.md +++ b/docs/diagrams/README.md @@ -151,4 +151,4 @@ Semantic decision data is stored in `.semantics/diagram.lock.json` (configurable - `theme` (default): grouped architecture themes per module. - `full`: resource-level detail. -Generated diagrams include semantic styling and a legend (Networking, Compute, Kubernetes, Storage, Access/RBAC). \ No newline at end of file +Generated diagrams include semantic styling and a legend (Networking, Compute, Kubernetes, Storage, Access/RBAC). diff --git a/docs/diagrams/scripts/generate_example_architecture.py b/docs/diagrams/scripts/generate_example_architecture.py index 3b97d10..620e8f9 100644 --- a/docs/diagrams/scripts/generate_example_architecture.py +++ b/docs/diagrams/scripts/generate_example_architecture.py @@ -1648,4 +1648,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/docs/diagrams/scripts/validate_stackit_flavors.py b/docs/diagrams/scripts/validate_stackit_flavors.py index 8d6461b..874baf4 100644 --- a/docs/diagrams/scripts/validate_stackit_flavors.py +++ b/docs/diagrams/scripts/validate_stackit_flavors.py @@ -276,4 +276,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/docs/getting-started.md b/docs/getting-started.md index 9cad7cd..2c8cce6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -371,4 +371,4 @@ resource "stackit_dns_record_set" "landing_zone_sample_gateway" { } ``` -This ensures a stable, Terraform-managed DNS path without external scripts until provider-native `gatewayApi` DNS extension support is available. \ No newline at end of file +This ensures a stable, Terraform-managed DNS path without external scripts until provider-native `gatewayApi` DNS extension support is available. diff --git a/mise.lock b/mise.lock index b172536..5ab15dc 100644 --- a/mise.lock +++ b/mise.lock @@ -1,81 +1,167 @@ # @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html [[tools."github:stackitcloud/stackit-cli"]] -version = "0.67.0" +version = "0.72.0" backend = "github:stackitcloud/stackit-cli" [tools."github:stackitcloud/stackit-cli"."platforms.linux-arm64"] -checksum = "sha256:ffd11efb49c1e26fc9b230b20762ae8fcd7c3a0abc7fea2bdca6c3c8c3027cad" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324192" +checksum = "sha256:baa1834009b43ab7c49680f865cec8bed31da4fd373fbfd3f816f30b460c8aeb" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902110" [tools."github:stackitcloud/stackit-cli"."platforms.linux-arm64-musl"] -checksum = "sha256:ffd11efb49c1e26fc9b230b20762ae8fcd7c3a0abc7fea2bdca6c3c8c3027cad" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324192" +checksum = "sha256:baa1834009b43ab7c49680f865cec8bed31da4fd373fbfd3f816f30b460c8aeb" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902110" [tools."github:stackitcloud/stackit-cli"."platforms.linux-x64"] -checksum = "sha256:248f053509d067e4e2e8b11c8a331dfe7f0392961ee59a782236cc17b5f9c9c6" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324187" +checksum = "sha256:8e3324606c13f063fb46ddff380bcc1a5fcdb7e230fd37fa021741f8c16d43d6" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902114" [tools."github:stackitcloud/stackit-cli"."platforms.linux-x64-musl"] -checksum = "sha256:248f053509d067e4e2e8b11c8a331dfe7f0392961ee59a782236cc17b5f9c9c6" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324187" +checksum = "sha256:8e3324606c13f063fb46ddff380bcc1a5fcdb7e230fd37fa021741f8c16d43d6" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902114" [tools."github:stackitcloud/stackit-cli"."platforms.macos-arm64"] -checksum = "sha256:006ef0ccc866544c6b6b01bda49b9e41b7e61d5c9cf6b72313816ac83ac069f7" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324196" +checksum = "sha256:6c55fa7aacc584a04ce932ed82cc8daf7e712a7a7f27477f33d298baf6ffbe96" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902117" [tools."github:stackitcloud/stackit-cli"."platforms.macos-x64"] -checksum = "sha256:f2a44a0ff3393c18e76159b5e0fe3a55042f17bda90900a282b3d2f69100f338" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324208" +checksum = "sha256:5f4f95ad5a4324826d0779aef572407485c5778e141a74fa5c17a89cc8d16e33" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902148" [tools."github:stackitcloud/stackit-cli"."platforms.windows-arm64"] -checksum = "sha256:d0c937bb48183df19a9bbb68f7512215cc924b4dc1f6e930397392881aadae92" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_windows_arm64.zip" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324155" +checksum = "sha256:4ccdb3fb6007afbf07c6a79ea4f7890d396f0b16f9dceac9324b2dc870536dfb" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_windows_arm64.zip" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902068" [tools."github:stackitcloud/stackit-cli"."platforms.windows-x64"] -checksum = "sha256:7d9f2eabb6d0e9b7e8238fbe56508dcc36264184301694c7e0bd4ab34aebaba7" -url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.67.0/stackit-cli_0.67.0_windows_amd64.zip" -url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/483324151" +checksum = "sha256:defcf6b716a48d0ff39ecbbfa9d17a68e7b080d6f7a15e254a43123a375a7014" +url = "https://github.com/stackitcloud/stackit-cli/releases/download/v0.72.0/stackit-cli_0.72.0_windows_amd64.zip" +url_api = "https://api.github.com/repos/stackitcloud/stackit-cli/releases/assets/528902073" [[tools.opentofu]] -version = "1.12.5" +version = "1.12.6" backend = "aqua:opentofu/opentofu" [tools.opentofu."platforms.linux-arm64"] -checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz" +checksum = "sha256:9bd0228a81bcd0c88f7045c74378f45a815779f19897191dff7d9efba9976b9e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762688" [tools.opentofu."platforms.linux-arm64-musl"] -checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz" +checksum = "sha256:9bd0228a81bcd0c88f7045c74378f45a815779f19897191dff7d9efba9976b9e" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762688" [tools.opentofu."platforms.linux-x64"] -checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz" +checksum = "sha256:50a6106fa4de523d09c87af85f3db1dd47535fc005727fdca6852146476b88ec" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762723" [tools.opentofu."platforms.linux-x64-musl"] -checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz" +checksum = "sha256:50a6106fa4de523d09c87af85f3db1dd47535fc005727fdca6852146476b88ec" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762723" [tools.opentofu."platforms.macos-arm64"] -checksum = "sha256:2ae38150a667f5c0bd57b318d18ad8091d08f93fcca40345f3d88998661de5a9" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_arm64.tar.gz" +checksum = "sha256:f958ec5e511063be9feb180ca015a4cb7977566a9cf6a8550bba8c2a9b5aba74" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762710" [tools.opentofu."platforms.macos-x64"] -checksum = "sha256:1012d8f3d4567bcbcd1f2c7d766feca39a30bced32fb8be47e1887fbbee2456d" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_amd64.tar.gz" +checksum = "sha256:44bb1855f372f17f365fb94517906e78da5001da10f4c98de57a39bf982f3a92" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762655" [tools.opentofu."platforms.windows-arm64"] -checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_windows_amd64.tar.gz" +checksum = "sha256:bcbcd9ca744af6c4053e2d5e75dae79ff33ad7dc38d45e1233e693d096284538" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_windows_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762708" [tools.opentofu."platforms.windows-x64"] -checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_windows_amd64.tar.gz" +checksum = "sha256:bcbcd9ca744af6c4053e2d5e75dae79ff33ad7dc38d45e1233e693d096284538" +url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_windows_amd64.tar.gz" +url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762708" + +[[tools.pre-commit]] +version = "4.6.2" +backend = "aqua:pre-commit/pre-commit" + +[tools.pre-commit."platforms.linux-arm64"] +checksum = "sha256:43c09a73d82af8efff09900e60d76d67a2c3d990613c3c770b2b5c4418a16c4b" +url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.2/pre-commit-4.6.2.pyz" +url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/509317714" + +[tools.pre-commit."platforms.linux-arm64-musl"] +checksum = "sha256:43c09a73d82af8efff09900e60d76d67a2c3d990613c3c770b2b5c4418a16c4b" +url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.2/pre-commit-4.6.2.pyz" +url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/509317714" + +[tools.pre-commit."platforms.linux-x64"] +checksum = "sha256:43c09a73d82af8efff09900e60d76d67a2c3d990613c3c770b2b5c4418a16c4b" +url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.2/pre-commit-4.6.2.pyz" +url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/509317714" + +[tools.pre-commit."platforms.linux-x64-musl"] +checksum = "sha256:43c09a73d82af8efff09900e60d76d67a2c3d990613c3c770b2b5c4418a16c4b" +url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.2/pre-commit-4.6.2.pyz" +url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/509317714" + +[tools.pre-commit."platforms.macos-arm64"] +checksum = "sha256:43c09a73d82af8efff09900e60d76d67a2c3d990613c3c770b2b5c4418a16c4b" +url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.2/pre-commit-4.6.2.pyz" +url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/509317714" + +[tools.pre-commit."platforms.macos-x64"] +checksum = "sha256:43c09a73d82af8efff09900e60d76d67a2c3d990613c3c770b2b5c4418a16c4b" +url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.2/pre-commit-4.6.2.pyz" +url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/509317714" + +[[tools.terraform-docs]] +version = "0.24.0" +backend = "aqua:terraform-docs/terraform-docs" + +[tools.terraform-docs."platforms.linux-arm64"] +checksum = "sha256:d12bd7b73c1fc9c64efc79f8157dd713dabd559f1ecf3cfc0f42e32279a155fd" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956212" + +[tools.terraform-docs."platforms.linux-arm64-musl"] +checksum = "sha256:d12bd7b73c1fc9c64efc79f8157dd713dabd559f1ecf3cfc0f42e32279a155fd" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956212" + +[tools.terraform-docs."platforms.linux-x64"] +checksum = "sha256:9005daf969de0b50134493a2c00078b49f5f5b39d021cda7c89bf4d4f3d776d3" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956208" + +[tools.terraform-docs."platforms.linux-x64-musl"] +checksum = "sha256:9005daf969de0b50134493a2c00078b49f5f5b39d021cda7c89bf4d4f3d776d3" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956208" + +[tools.terraform-docs."platforms.macos-arm64"] +checksum = "sha256:f6b114f4b032f3f9202ab6c23bfd28c3c8e68aeeb8a8f12fc118bf2073081d71" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956237" + +[tools.terraform-docs."platforms.macos-x64"] +checksum = "sha256:3c3f7f18f908457fd1209cbe341418f7f6bae78c08126cfbe8de0d1b06aa8781" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-darwin-amd64.tar.gz" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956256" + +[tools.terraform-docs."platforms.windows-arm64"] +checksum = "sha256:1168c12929531ac9132c97fff794ef531ebb2a5b54c41870b9e19911950b6fd4" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-windows-arm64.zip" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956211" + +[tools.terraform-docs."platforms.windows-x64"] +checksum = "sha256:afc02cbdf63726d3e5fb26a077ca1f24acd4820d7e44c21b03a3e030f9266490" +url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-windows-amd64.zip" +url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956245" diff --git a/mise.toml b/mise.toml index bd5b1a6..a10bd87 100644 --- a/mise.toml +++ b/mise.toml @@ -1,6 +1,8 @@ [tools] -opentofu = "1.12.5" -"github:stackitcloud/stackit-cli" = "0.67.0" +opentofu = "1.12.6" +"github:stackitcloud/stackit-cli" = "0.72.0" +pre-commit = "4.6.2" +terraform-docs = "0.24.0" [env] -STACKIT_SERVICE_ACCOUNT_KEY_PATH = "{{env.HOME}}/.stackit/credentials.json" \ No newline at end of file +STACKIT_SERVICE_ACCOUNT_KEY_PATH = "{{env.HOME}}/.stackit/credentials.json" diff --git a/src/README.md b/src/README.md index 52627e6..a27eda9 100644 --- a/src/README.md +++ b/src/README.md @@ -2,63 +2,118 @@ ## Requirements | Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | 0.98.0 | -| [time](#requirement\_time) | 0.14.0 | -| [vault](#requirement\_vault) | 5.9.0 | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [grafana](#requirement\_grafana) | 4.45.2 | +| [helm](#requirement\_helm) | 3.3.0 | +| [kubernetes](#requirement\_kubernetes) | 3.2.1 | +| [opnsense](#requirement\_opnsense) | 0.26.0 | +| [random](#requirement\_random) | 3.9.0 | +| [stackit](#requirement\_stackit) | 0.114.0 | +| [time](#requirement\_time) | 0.14.1 | +| [vault](#requirement\_vault) | 5.11.0 | ## Providers -No providers. +| Name | Version | +| ---- | ------- | +| [helm.platform](#provider\_helm.platform) | 3.3.0 | +| [kubernetes.platform](#provider\_kubernetes.platform) | 3.2.1 | +| [stackit](#provider\_stackit) | 0.114.0 | +| [terraform](#provider\_terraform) | n/a | +| [vault](#provider\_vault) | 5.11.0 | ## Modules | Name | Source | Version | -|------|--------|---------| +| ---- | ------ | ------- | | [connectivity](#module\_connectivity) | ./modules/connectivity | n/a | | [devops](#module\_devops) | ./modules/devops | n/a | +| [firewall\_config](#module\_firewall\_config) | ./modules/firewall-config | 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 | +| [namespace\_service\_demo](#module\_namespace\_service\_demo) | ./modules/namespace-service-demo | n/a | +| [platform\_kubernetes](#module\_platform\_kubernetes) | ./modules/platform-kubernetes | n/a | | [sandboxes](#module\_sandboxes) | ./modules/sandboxes | n/a | ## Resources -No resources. +| Name | Type | +| ---- | ---- | +| [helm_release.demo_envoy_gateway](https://registry.terraform.io/providers/hashicorp/helm/3.3.0/docs/resources/release) | resource | +| [helm_release.kyverno](https://registry.terraform.io/providers/hashicorp/helm/3.3.0/docs/resources/release) | resource | +| [kubernetes_manifest.landing_zone_gateway_class](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/manifest) | resource | +| [kubernetes_manifest.landing_zone_sample_gateway](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/manifest) | resource | +| [kubernetes_manifest.landing_zone_sample_http_route](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/manifest) | resource | +| [kubernetes_manifest.landing_zone_secret_enforcement_policy](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/manifest) | resource | +| [kubernetes_namespace_v1.landing_zone](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/namespace_v1) | resource | +| [kubernetes_pod_v1.landing_zone_sample_load](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/pod_v1) | resource | +| [kubernetes_role_binding_v1.landing_zone_user](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/role_binding_v1) | resource | +| [kubernetes_role_v1.landing_zone_user](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/role_v1) | resource | +| [kubernetes_secret_v1.landing_zone_user_token](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/secret_v1) | resource | +| [kubernetes_service_account_v1.landing_zone_user](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/service_account_v1) | resource | +| [kubernetes_service_v1.landing_zone_sample_load](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/service_v1) | resource | +| [stackit_dns_record_set.landing_zone_sample_gateway](https://registry.terraform.io/providers/stackitcloud/stackit/0.114.0/docs/resources/dns_record_set) | resource | +| [terraform_data.firewall_api_bootstrap](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | +| [vault_kv_secret_v2.firewall_api_credentials](https://registry.terraform.io/providers/hashicorp/vault/5.11.0/docs/resources/kv_secret_v2) | resource | +| [kubernetes_resources.landing_zone_sample_gateway_service](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/data-sources/resources) | data source | ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | +| [audit\_logs](#input\_audit\_logs) | Audit log routing for the management module. A Telemetry Link streams audit events into a Telemetry Router, which forwards them to a Logs instance and archives them to object storage. link\_scopes defaults to a single organization-wide link. retention\_days applies to both the Logs instance and, when s3\_object\_lock is enabled, the archive bucket's default retention. s3\_object\_lock is off by default and turns on GOVERNANCE-mode WORM protection for the archive bucket, which is not cleanly reversible. Set to null to skip audit logs deployment. |
object({
retention_days = optional(number, 180)
acl = optional(list(string), ["0.0.0.0/0"])
s3_object_lock = optional(bool, true)
link_scopes = optional(list(object({
resource_type = string # organization, folder, project
resource_id = string
})), null)
})
| `null` | no | | [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 | +| [connectivity](#input\_connectivity) | Connectivity configuration including DNS zones, network area, firewall, and VPN. Set firewall/network\_area/vpn 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)
# Unset means the STACKIT resolvers of var.region, see modules/connectivity/1-network-area.tf.
default_nameservers = optional(list(string), null)
}), 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)

# Active/passive CARP pair. Adds a second appliance in backup_zone and a CARP
# virtual IP on the LAN that replaces the primary's LAN IP as the next hop the
# landing zone routes point at, so a node failure never touches the routes.
# See docs/architecture.md#high-availability-optional.
ha = optional(object({
backup_zone = string
backup_name = optional(string, null) # defaults to "-backup"
backup_lan_ip = optional(string, null) # defaults to the 6th address of lan_network_range
backup_wan_ip = optional(string, null) # defaults to the 6th address of wan_network_range
lan_vip = optional(string, null) # defaults to the 7th address of lan_network_range
vhid = optional(number, 1)
}), null)
}), null)
vpn = optional(object({
display_name = optional(string, null)
plan_id = optional(string, "p100")
routing_type = optional(string, "ROUTE_BASED")
availability_zones = object({
tunnel1 = string
tunnel2 = string
})
connections = optional(map(object({
display_name = optional(string, null)
enabled = optional(bool, true)
local_subnets = optional(list(string), null)
remote_subnets = optional(list(string), null)
static_routes = optional(list(string), null)
tunnel1 = object({
remote_address = string
peering = optional(object({
local_address = string
remote_address = string
}), null)
phase1 = optional(object({
encryption_algorithms = optional(list(string), ["aes256"])
integrity_algorithms = optional(list(string), ["sha2_384"])
dh_groups = optional(list(string), ["ecp384"])
rekey_time = optional(number, null)
}), {})
phase2 = optional(object({
encryption_algorithms = optional(list(string), ["aes256"])
integrity_algorithms = optional(list(string), ["sha2_384"])
dh_groups = optional(list(string), ["ecp384"])
rekey_time = optional(number, null)
dpd_action = optional(string, null)
start_action = optional(string, null)
}), {})
})
tunnel2 = object({
remote_address = string
peering = optional(object({
local_address = string
remote_address = string
}), null)
phase1 = optional(object({
encryption_algorithms = optional(list(string), ["aes256"])
integrity_algorithms = optional(list(string), ["sha2_384"])
dh_groups = optional(list(string), ["ecp384"])
rekey_time = optional(number, null)
}), {})
phase2 = optional(object({
encryption_algorithms = optional(list(string), ["aes256"])
integrity_algorithms = optional(list(string), ["sha2_384"])
dh_groups = optional(list(string), ["ecp384"])
rekey_time = optional(number, null)
dpd_action = optional(string, null)
start_action = 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 | | [federated\_identity\_providers](#input\_federated\_identity\_providers) | List of federated identity providers to configure for the management service account. |
list(object({
name = string
issuer = string
assertions = list(object({
item = string
operator = string
value = string
}))
}))
| `[]` | no | +| [firewall\_admin\_password](#input\_firewall\_admin\_password) | Password of firewall\_admin\_username, used once to derive the API key. Defaults to the password baked into the STACKIT OPNsense image. Override through TF\_VAR\_firewall\_admin\_password after rotating it, or set to null and supply firewall\_api\_credentials instead. | `string` | `"STACKIT123!"` | no | +| [firewall\_admin\_username](#input\_firewall\_admin\_username) | Login used to derive the API key from the appliance. The STACKIT OPNsense image ships with root. | `string` | `"root"` | no | +| [firewall\_api\_credentials](#input\_firewall\_api\_credentials) | OPNsense API key and secret. Bootstrap them once from the appliance login with src/modules/firewall-config/scripts/bootstrap-api-key.sh, then supply through TF\_VAR\_firewall\_api\_credentials or a gitignored tfvars file. Kept out of firewall\_config so the policy stays committable. |
object({
api_key = string
api_secret = string
})
| `null` | no | +| [firewall\_api\_secret\_version](#input\_firewall\_api\_secret\_version) | Write version of the OPNsense API key in the Secrets Manager. The key is only transmitted when this number changes, so bump it by one after a re-bootstrap has produced a fresh key, otherwise the Secrets Manager keeps serving the old one. | `number` | `1` | no | +| [firewall\_bootstrap](#input\_firewall\_bootstrap) | Pass -var firewall\_bootstrap=true on the two greenfield applies that create the OPNsense API key. It suppresses the Secrets Manager read, which would otherwise fail at plan time because the secret does not exist yet. Never set it on an established deployment: without the bootstrap cache file it plans the destruction of the policy and the stored key. The steady state needs no variable at all. | `bool` | `false` | no | +| [firewall\_config](#input\_firewall\_config) | Policy pushed to the OPNsense appliance through its API. Requires connectivity.firewall to be deployed and firewall\_api\_credentials to be set. Set to null to leave the appliance untouched. |
object({
# Defaults to https://, derived from connectivity.firewall.
endpoint = optional(string, null)
insecure = optional(bool, true)


aliases = optional(map(object({
type = optional(string, "network")
enabled = optional(bool, true)
description = optional(string, null)
content = optional(list(string), [])
update_freq = optional(number, null)
stats = optional(bool, false)
})), {})

routes = optional(map(object({
enabled = optional(bool, true)
network = string
gateway = string
description = optional(string, null)
})), {})

rules = optional(map(object({
sequence = optional(number, 100)
enabled = optional(bool, true)
action = optional(string, "pass")
direction = optional(string, "in")
interfaces = optional(list(string), ["lan"])
protocol = optional(string, "any")
ip_protocol = optional(string, "inet")
quick = optional(bool, true)
source_net = optional(string, "any")
source_port = optional(string, null)
source_invert = optional(bool, false)
destination_net = optional(string, "any")
destination_port = optional(string, null)
destination_invert = optional(bool, false)
log = optional(bool, false)
description = optional(string, null)
})), {})

outbound_nat = optional(map(object({
sequence = optional(number, 200)
enabled = optional(bool, true)
interface = optional(string, "wan")
protocol = optional(string, "any")
ip_protocol = optional(string, "inet")
source_net = optional(string, "any")
destination_net = optional(string, "any")
target_ip = optional(string, "wanip")
disable_nat = optional(bool, false)
log = optional(bool, false)
description = optional(string, null)
})), {})

port_forwards = optional(map(object({
sequence = optional(number, 100)
enabled = optional(bool, true)
interfaces = optional(list(string), ["wan"])
protocol = optional(string, "TCP")
ip_protocol = optional(string, "inet")
source_net = optional(string, "any")
destination_net = optional(string, "wanip")
destination_port = string
target_ip = string
target_port = optional(string, null)
nat_reflection = optional(string, "default")
log = optional(bool, true)
description = optional(string, null)
})), {})
})
| `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 | +| [landing\_zone\_namespace\_services](#input\_landing\_zone\_namespace\_services) | Map of namespace-service configurations per landing zone key. If a key is present, namespace service is enabled for that landing zone. |
map(object({
demo_enabled = optional(bool, false)
demo_metrics_ingestion = optional(object({
enabled = optional(bool, false)
target_urls = optional(list(string), [])
scheme = optional(string, "https")
metrics_path = optional(string, "/")
scrape_interval = optional(string, "70s")
scrape_timeout = optional(string, "30s")
}), {})
namespace = optional(string, null)
dns_subdomain = optional(string, null)
secretsmanager = optional(bool, true)
sample_load = optional(object({
enabled = optional(bool, false)
image = optional(string, "busybox:1.36")
}), {})
secrets_enforcement = optional(object({
enabled = optional(bool, false)
mode = optional(string, "audit")
allow_opaque_secret_types = optional(list(string), [])
break_glass = optional(object({
enabled = optional(bool, true)
ttl_hours = optional(number, 24)
principals = optional(list(string), [])
}), {})
}), {})
kubernetes_access = optional(object({
enabled = optional(bool, true)
service_account_name = optional(string, null)
}), {})
labels = optional(map(string), {})
annotations = optional(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)
})), [])
observability = optional(object({
enabled = optional(bool, false)
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
name = optional(string, null)
}), {})
}))
| `{}` | 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 | +| [platform\_kubernetes](#input\_platform\_kubernetes) | Map of central, region-scoped platform Kubernetes deployments. Empty map skips deployment. |
map(object({
region = string
network = optional(object({
sna_enabled = optional(bool, false)
sna_network_area_id = optional(string, null)
firewall_next_hop_ip = optional(string, null)
sna_network_prefix_length = optional(number, 24)
}), {})
dns = optional(object({
enabled = optional(bool, true)
create_zones = optional(bool, true)
zones = optional(list(string), [])
}), {})
observability = optional(object({
enabled = optional(bool, true)
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
name = optional(string, null)
}), {})
encrypted_volumes = optional(object({
enabled = optional(bool, false)
storage_class_name = optional(string, "stackit-encrypted-premium")
kms_keyring_name = optional(string, "ske-volume-keyring")
kms_key_name = optional(string, "ske-volume-key")
kms_key_version = optional(string, "1")
}), {})
debug_bastion = optional(object({
enabled = optional(bool, false)
name = optional(string, null)
availability_zone = optional(string, null)
machine_type = optional(string, "g2i.1")
image_id = optional(string, "7b10e105-295b-4369-b6e0-567ec940a02b")
boot_volume_size = optional(number, 20)
ssh_public_key = optional(string, null)
ssh_public_key_path = optional(string, "~/.ssh/id_rsa.pub")
ssh_allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
assign_public_ip = optional(bool, true)
install_kubectl = optional(bool, true)
}), {})
role_assignments = optional(list(object({
role = string
subject = string
})), [])
cluster = object({
name = string
kubernetes_version_min = optional(string, null)
node_pools = optional(list(object({
name = string
machine_type = string
minimum = number
maximum = number
availability_zones = list(string)
allow_system_components = optional(bool, false)
volume_size = optional(number, 20)
volume_type = optional(string, "storage_premium_perf1")
os_name = optional(string, "flatcar")
labels = optional(map(string), {})
})), [
{
name = "system"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-1"]
allow_system_components = true
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "system"
}
},
{
name = "application"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-2"]
allow_system_components = false
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "application"
}
}
])
maintenance = optional(object({
enable_kubernetes_version_updates = optional(bool, true)
enable_machine_image_version_updates = optional(bool, true)
start = optional(string, "01:00:00Z")
end = optional(string, "02:00:00Z")
}), {})
})
}))
| `{}` | no | +| [platform\_kubernetes\_kube\_config\_override](#input\_platform\_kubernetes\_kube\_config\_override) | Optional raw kubeconfig used by kubernetes/helm platform providers when no platform\_kubernetes module output is available. | `string` | `null` | no | | [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no | | [rm\_folder\_parent\_id](#input\_rm\_folder\_parent\_id) | ID of the parent folder under which the resource manager folders will be created. If not provided, the resource manager folders will be created under the organization. | `string` | `null` | no | | [rm\_folders](#input\_rm\_folders) | Map of resource manager folders to create under the root organization. |
map(object({
name = string
description = optional(string, null)
owner_emails = list(string)
reader_emails = list(string)
}))
|
{
"landing_zones_corporate": {
"name": "Landing Zones - Corporate",
"owner_emails": [],
"reader_emails": []
},
"landing_zones_public": {
"name": "Landing Zones - Public",
"owner_emails": [],
"reader_emails": []
},
"platform": {
"name": "Platform",
"owner_emails": [],
"reader_emails": []
},
"sandboxes": {
"name": "Sandboxes",
"owner_emails": [],
"reader_emails": []
}
}
| no | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | +| [vpn\_pre\_shared\_keys](#input\_vpn\_pre\_shared\_keys) | Pre-shared keys per connectivity.vpn.connections key, one per tunnel. Kept out of the connectivity object so the topology stays committable; supply through TF\_VAR\_vpn\_pre\_shared\_keys or a gitignored tfvars file. Minimum 20 characters. |
map(object({
tunnel1 = string
tunnel2 = string
}))
| `{}` | no | ## Outputs | Name | Description | -|------|-------------| -| [connectivity\_firewall\_public\_ip](#output\_connectivity\_firewall\_public\_ip) | The public IP of the firewall. | +| ---- | ----------- | +| [audit\_logs\_datasource\_url](#output\_audit\_logs\_datasource\_url) | Datasource URL of the audit Logs instance, usable as a Grafana datasource. | +| [audit\_logs\_instance\_id](#output\_audit\_logs\_instance\_id) | The ID of the Logs instance receiving audit logs. Null when audit\_logs is not configured. | +| [audit\_telemetry\_router\_id](#output\_audit\_telemetry\_router\_id) | The ID of the Telemetry Router collecting audit logs. | +| [connectivity\_firewall\_backup\_public\_ip](#output\_connectivity\_firewall\_backup\_public\_ip) | The public IP of the backup firewall. Null without connectivity.firewall.ha. It does not take over the primary's address on failover; egress from the backup is translated to this one. | +| [connectivity\_firewall\_next\_hop\_ip](#output\_connectivity\_firewall\_next\_hop\_ip) | The next hop the corporate landing zone routes point at. The CARP LAN VIP under HA, otherwise the firewall LAN IP. | +| [connectivity\_firewall\_public\_ip](#output\_connectivity\_firewall\_public\_ip) | The public IP of the firewall (primary node). | | [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. | +| [connectivity\_vpn\_connection\_ids](#output\_connectivity\_vpn\_connection\_ids) | Map of hub VPN connection keys to their connection IDs. | +| [connectivity\_vpn\_gateway\_id](#output\_connectivity\_vpn\_gateway\_id) | The ID of the hub VPN gateway. | +| [connectivity\_vpn\_internal\_next\_hop\_ips](#output\_connectivity\_vpn\_internal\_next\_hop\_ips) | Network area side IPs of the hub VPN gateway tunnels. | +| [connectivity\_vpn\_public\_ips](#output\_connectivity\_vpn\_public\_ips) | Public IPs of the hub VPN gateway tunnels. Configure the remote peer against these. | | [devops\_project\_id](#output\_devops\_project\_id) | The project ID of the DevOps project. | +| [firewall\_admin\_url](#output\_firewall\_admin\_url) | Where to reach the OPNsense web GUI. Only reachable from inside the network area once the policy blocks the WAN. | | [governance\_folder\_ids](#output\_governance\_folder\_ids) | Map of governance folder names to their container IDs. | +| [landing\_zone\_namespace\_demo\_samples](#output\_landing\_zone\_namespace\_demo\_samples) | Demo sample references for namespace services. | | [landing\_zone\_projects](#output\_landing\_zone\_projects) | Map of landing zone project IDs. | | [management\_bucket\_name\_tfstate](#output\_management\_bucket\_name\_tfstate) | The name of the Management tfstate object storage bucket. | | [management\_project\_id](#output\_management\_project\_id) | The project ID of the Management project. | +| [platform\_kubernetes\_projects](#output\_platform\_kubernetes\_projects) | Map of platform Kubernetes projects and cluster metadata per key. | | [sandbox\_projects](#output\_sandbox\_projects) | The created sandbox projects. | - \ No newline at end of file + diff --git a/src/_firewall-bootstrap.tf b/src/_firewall-bootstrap.tf index 6c8976a..83bd773 100644 --- a/src/_firewall-bootstrap.tf +++ b/src/_firewall-bootstrap.tf @@ -156,4 +156,4 @@ locals { description = "pfsync state replication between the HA pair" }) } : {} -} \ No newline at end of file +} diff --git a/src/_landing-zone-kubernetes.tf b/src/_landing-zone-kubernetes.tf index 36e744b..2415576 100644 --- a/src/_landing-zone-kubernetes.tf +++ b/src/_landing-zone-kubernetes.tf @@ -2,15 +2,6 @@ ## LANDING ZONE ON KUBERNETES ## ################################ -# The Kubernetes side of a landing zone: every entry in landing_zone_namespace_services -# gets a tenant slice of the shared platform cluster — its own namespace, a scoped service -# account with a Role limited to that namespace, and optionally a Kyverno policy that -# blocks direct Secret management so credentials have to come through the Secrets Manager. -# -# The sample workload behind sample_load is demo material, not part of the landing zone -# contract: a pod, a Gateway API route and a DNS record that together prove the path from -# the internet to a namespace works. Drop it once real workloads move in. - locals { secrets_enforcement_default_exempt_principals = [ "system:serviceaccount:external-secrets:external-secrets", diff --git a/src/config/hub-and-spoke-firewall.tfvars b/src/config/hub-and-spoke-firewall.tfvars index c4d5360..91390f4 100644 --- a/src/config/hub-and-spoke-firewall.tfvars +++ b/src/config/hub-and-spoke-firewall.tfvars @@ -3,7 +3,7 @@ ###################### # Email of the technical owner registered in STACKIT -owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" +owner_email = "owner@example.com" # Company name used for folder naming in the resource manager company_name = "Example Corp" @@ -12,7 +12,7 @@ company_name = "Example Corp" company_code = "exc" # Root organization container ID from STACKIT resource manager -organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" +organization_id = "00000000-0000-0000-0000-000000000000" region = "eu01" @@ -339,7 +339,7 @@ connectivity = { sandboxes = [ { project_name = "Sandbox Team Alpha" - project_owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + project_owner_email = "owner@example.com" } ] @@ -351,7 +351,7 @@ landing_zones = { "corp-exmpl" = { project_name = "Data Platform" project_code = "data" - owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + owner_email = "owner@example.com" env = "prod" # Set corporate = true for network area connectivity, false for public internet @@ -363,7 +363,7 @@ landing_zones = { "public-exmpl" = { project_name = "External API Gateway" project_code = "api" - owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + owner_email = "owner@example.com" env = "prod" corporate = false @@ -374,4 +374,4 @@ landing_zones = { # } # ] } -} \ No newline at end of file +} diff --git a/src/config/hub-and-spoke.tfvars b/src/config/hub-and-spoke.tfvars index a22e54a..5f5525a 100644 --- a/src/config/hub-and-spoke.tfvars +++ b/src/config/hub-and-spoke.tfvars @@ -3,7 +3,7 @@ ###################### # Email of the technical owner registered in STACKIT -owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" +owner_email = "owner@example.com" # Company name used for folder naming in the resource manager company_name = "Example Corp" @@ -12,7 +12,7 @@ company_name = "Example Corp" company_code = "exc" # Root organization container ID from STACKIT resource manager -organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" +organization_id = "00000000-0000-0000-0000-000000000000" region = "eu01" @@ -148,7 +148,7 @@ connectivity = { sandboxes = [ { project_name = "Sandbox Team Alpha" - project_owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + project_owner_email = "owner@example.com" } ] @@ -160,7 +160,7 @@ landing_zones = { "corp-exmpl" = { project_name = "Data Platform" project_code = "data" - owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + owner_email = "owner@example.com" env = "prod" # Set corporate = true for network area connectivity, false for public internet @@ -173,7 +173,7 @@ landing_zones = { "public-exmpl" = { project_name = "External API Gateway" project_code = "api" - owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + owner_email = "owner@example.com" env = "prod" corporate = false @@ -193,4 +193,4 @@ landing_zones = { # dns_subdomain = "app" # secretsmanager = true # } -# } \ No newline at end of file +# } diff --git a/src/config/standalone.tfvars b/src/config/standalone.tfvars index a662541..1935928 100644 --- a/src/config/standalone.tfvars +++ b/src/config/standalone.tfvars @@ -3,7 +3,7 @@ ###################### # Email of the technical owner registered in STACKIT -owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" +owner_email = "owner@example.com" # Company name used for folder naming in the resource manager company_name = "Example Corp" @@ -12,7 +12,7 @@ company_name = "Example Corp" company_code = "exc" # Root organization container ID from STACKIT resource manager -organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" +organization_id = "00000000-0000-0000-0000-000000000000" region = "eu01" @@ -68,7 +68,7 @@ labels = { sandboxes = [ { project_name = "Sandbox Team Alpha" - project_owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + project_owner_email = "owner@example.com" } ] @@ -80,7 +80,7 @@ landing_zones = { "public-exmpl" = { project_name = "External API Gateway" project_code = "api" - owner_email = "eu01-fhnnk51@ske.sa.stackit.cloud" + owner_email = "owner@example.com" env = "prod" # role_assignments = [ @@ -90,4 +90,4 @@ landing_zones = { # } # ] } -} \ No newline at end of file +} diff --git a/src/main.tf b/src/main.tf index 47d2948..a6f4627 100644 --- a/src/main.tf +++ b/src/main.tf @@ -168,5 +168,5 @@ module "landing_zone" { ipv4_nameservers = try(module.connectivity[0].network_area_nameservers, null) custom_roles = each.value.custom_roles observability = each.value.observability - 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 + firewall_next_hop_ip = var.connectivity != null && var.connectivity.firewall != null ? module.connectivity[0].firewall_next_hop_ip : null } diff --git a/src/modules/connectivity/3-external-network.tf b/src/modules/connectivity/3-external-network.tf index 93b921b..fa0b757 100644 --- a/src/modules/connectivity/3-external-network.tf +++ b/src/modules/connectivity/3-external-network.tf @@ -88,4 +88,4 @@ resource "stackit_public_ip" "wan-ip_backup" { project_id = stackit_resourcemanager_project.this.project_id network_interface_id = stackit_network_interface.wan_backup[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 index 1192bfb..1abbb66 100644 --- a/src/modules/connectivity/4-internal-network.tf +++ b/src/modules/connectivity/4-internal-network.tf @@ -33,4 +33,4 @@ resource "stackit_network_interface" "lan_backup" { network_id = stackit_network.lan[0].network_id ipv4 = local.firewall_backup_lan_ip security = false -} \ No newline at end of file +} diff --git a/src/modules/connectivity/6-dns-zones.tf b/src/modules/connectivity/6-dns-zones.tf index 4d3a0ca..07f68ee 100644 --- a/src/modules/connectivity/6-dns-zones.tf +++ b/src/modules/connectivity/6-dns-zones.tf @@ -13,4 +13,4 @@ resource "stackit_dns_zone" "this" { 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 index 6a275b7..11e2e99 100644 --- a/src/modules/connectivity/README.md +++ b/src/modules/connectivity/README.md @@ -2,17 +2,20 @@ ## Requirements | Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | 0.101.0 | -| [time](#requirement\_time) | ~> 0.14.0 | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [random](#requirement\_random) | >= 3.9.0 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | +| [time](#requirement\_time) | >= 0.14.1 | ## Providers | Name | Version | -|------|---------| +| ---- | ------- | +| [random](#provider\_random) | >= 3.9.0 | | [stackit](#provider\_stackit) | 0.93.0 | -| [time](#provider\_time) | ~> 0.14.0 | +| [terraform](#provider\_terraform) | n/a | +| [time](#provider\_time) | >= 0.14.1 | ## Modules @@ -21,34 +24,45 @@ No modules. ## Resources | Name | Type | -|------|------| -| [stackit_authorization_project_role_assignment.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/authorization_project_role_assignment) | resource | -| [stackit_dns_zone.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/dns_zone) | resource | -| [stackit_image.firewall](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/image) | resource | -| [stackit_network.lan](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/network) | resource | -| [stackit_network.wan](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/network) | resource | -| [stackit_network_area.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/network_area) | resource | -| [stackit_network_area_region.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/network_area_region) | resource | -| [stackit_network_interface.lan](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/network_interface) | resource | -| [stackit_network_interface.wan](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/network_interface) | resource | -| [stackit_public_ip.wan-ip](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/public_ip) | resource | -| [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/resourcemanager_project) | resource | -| [stackit_routing_table.wan](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/routing_table) | resource | -| [stackit_routing_table_route.wan](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/routing_table_route) | resource | -| [stackit_server.firewall](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/server) | resource | -| [stackit_volume.firewall](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/volume) | resource | -| [stackit_vpn_connection.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/vpn_connection) | resource | -| [stackit_vpn_gateway.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/resources/vpn_gateway) | resource | +| ---- | ---- | +| [random_password.carp](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | 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_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.lan_backup](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_network_interface.wan_backup](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_public_ip.wan-ip_backup](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_server.firewall_backup](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 | +| [stackit_volume.firewall_backup](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/volume) | resource | +| [stackit_vpn_connection.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/vpn_connection) | resource | +| [stackit_vpn_gateway.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/vpn_gateway) | resource | +| [terraform_data.firewall_ha_backup](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | +| [terraform_data.firewall_ha_primary](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | 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 | -| [stackit_vpn_gateway_status.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.101.0/docs/data-sources/vpn_gateway_status) | data source | +| [stackit_vpn_gateway_status.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/data-sources/vpn_gateway_status) | data source | ## 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 | +| [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). Set ha for an active/passive CARP pair; the LAN VIP then replaces the primary's LAN IP as the platform next hop. |
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)

# Active/passive HA. Adds a second appliance in backup_zone plus a CARP virtual IP on
# the LAN that becomes the platform next hop. CARP runs in unicast mode (OPNsense
# >= 24.7) because the STACKIT fabric does not deliver advertisements sourced from
# the shared virtual MAC, so multicast CARP splits the brain.
# Node-local settings are pushed by scripts/configure-ha.sh during apply.
ha = optional(object({
backup_zone = string
backup_name = optional(string, null) # defaults to "-backup"
backup_lan_ip = optional(string, null) # defaults to the 6th address of lan_network_range
backup_wan_ip = optional(string, null) # defaults to the 6th address of wan_network_range
lan_vip = optional(string, null) # defaults to the 7th address of lan_network_range
vhid = optional(number, 1)
}), null)
})
| `null` | no | +| [firewall\_admin\_endpoint](#input\_firewall\_admin\_endpoint) | Base URL the HA configuration logs into the primary appliance with. Defaults to the primary's public IP, which is the only address reachable from outside the network area. Set it to the LAN address when OpenTofu runs inside the area, matching firewall\_config.endpoint. The backup node is always configured over its own public IP: before HA exists it has no other reachable address. | `string` | `null` | no | +| [firewall\_admin\_password](#input\_firewall\_admin\_password) | Password of firewall\_admin\_username. Defaults to the password baked into the STACKIT OPNsense image. Only used when firewall.ha is set. | `string` | `"STACKIT123!"` | no | +| [firewall\_admin\_username](#input\_firewall\_admin\_username) | Appliance login used to push the node-local HA settings. The STACKIT OPNsense image ships with root. Only used when firewall.ha is set. | `string` | `"root"` | 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. default\_nameservers falls back to the STACKIT resolvers of var.region when unset. |
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), null)
})
| n/a | yes | @@ -65,11 +79,13 @@ No modules. ## 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 LAN IP). | -| [firewall\_public\_ip](#output\_firewall\_public\_ip) | The public IP address of the firewall WAN interface. | +| [firewall\_backup\_public\_ip](#output\_firewall\_backup\_public\_ip) | The public IP address of the backup firewall's WAN interface. Null without HA. | +| [firewall\_cluster\_lan\_ips](#output\_firewall\_cluster\_lan\_ips) | LAN addresses of the firewall HA pair, for the fw\_cluster alias in the policy. Empty without HA. | +| [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. The CARP LAN VIP under HA, otherwise the firewall LAN IP. | +| [firewall\_public\_ip](#output\_firewall\_public\_ip) | The public IP address of the firewall WAN interface (primary node). | | [network\_area\_id](#output\_network\_area\_id) | The ID of the created network area. | | [network\_area\_nameservers](#output\_network\_area\_nameservers) | Resolvers configured as the network area default, either from network\_area.default\_nameservers or the STACKIT resolvers of the region. | | [project\_container\_id](#output\_project\_container\_id) | The container ID of the created STACKIT project. | @@ -79,4 +95,4 @@ No modules. | [vpn\_gateway\_id](#output\_vpn\_gateway\_id) | The ID of the VPN gateway in the hub. | | [vpn\_internal\_next\_hop\_ips](#output\_vpn\_internal\_next\_hop\_ips) | Map of VPN tunnel names to their network area side IP. Ping targets to verify a tunnel carries traffic into the SNA. | | [vpn\_public\_ips](#output\_vpn\_public\_ips) | Map of VPN tunnel names to their public IP. These are the addresses the remote peer has to be configured against. | - \ No newline at end of file + diff --git a/src/modules/connectivity/scripts/configure-ha.sh b/src/modules/connectivity/scripts/configure-ha.sh index 0845e72..cd5af83 100644 --- a/src/modules/connectivity/scripts/configure-ha.sh +++ b/src/modules/connectivity/scripts/configure-ha.sh @@ -4,10 +4,10 @@ # and the pfsync/XMLRPC synchronization settings. Everything else (rules, NAT, aliases) # reaches the backup through the XMLRPC sync this script switches on. # -# CARP runs in unicast mode on purpose. Measured on STACKIT (2026-08-02): the fabric -# delivers frames *to* the CARP virtual MAC, but advertisements sourced *from* the shared -# virtual MAC never reach the peer — multicast CARP therefore splits the brain, while -# unicast advertisements (sent from the real MAC) elect cleanly with sub-second failover. +# CARP runs in unicast mode. On STACKIT the fabric delivers frames *to* the CARP virtual +# MAC, but advertisements sourced *from* the shared virtual MAC never reach the peer: +# multicast CARP splits the brain, while unicast advertisements (sent from the real MAC) +# elect cleanly with sub-second failover. # # Authenticates with the admin login through a GUI session cookie + CSRF token, exactly # like ../../firewall-config/scripts/bootstrap-api-key.sh, so it works on a node the OpenTofu provider has no API key @@ -145,9 +145,9 @@ if [[ "$ROLE" == "primary" ]]; then want[username]="$USERNAME" want[password]="$PASSWORD" # Exactly the sections firewall-config manages. An empty syncitems does NOT mean - # "sync everything" on OPNsense 26.1: measured 2026-08-04, the XMLRPC sync then - # replicates nothing at all while still answering {"status":"ok"} — the backup would - # sit without a policy and black-hole traffic the moment it becomes MASTER. + # "sync everything" on OPNsense 26.1: the XMLRPC sync then replicates nothing at all + # while still answering {"status":"ok"}, and the backup sits without a policy and + # black-holes traffic the moment it becomes MASTER. want[syncitems]="aliases,categories,rules,nat,staticroutes" fi diff --git a/src/modules/connectivity/terraform.tf b/src/modules/connectivity/terraform.tf index a131f20..bf848a8 100644 --- a/src/modules/connectivity/terraform.tf +++ b/src/modules/connectivity/terraform.tf @@ -1,18 +1,18 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } time = { source = "hashicorp/time" - version = "~> 0.14.0" + version = ">= 0.14.1" } random = { source = "hashicorp/random" - version = "3.9.0" + version = ">= 3.9.0" } } -} \ No newline at end of file +} diff --git a/src/modules/connectivity/variables.tf b/src/modules/connectivity/variables.tf index f620ff3..a4c2a4a 100644 --- a/src/modules/connectivity/variables.tf +++ b/src/modules/connectivity/variables.tf @@ -27,7 +27,7 @@ variable "firewall" { # Active/passive HA. Adds a second appliance in backup_zone plus a CARP virtual IP on # the LAN that becomes the platform next hop. CARP runs in unicast mode (OPNsense # >= 24.7) because the STACKIT fabric does not deliver advertisements sourced from - # the shared virtual MAC — multicast CARP splits the brain (measured 2026-08-02). + # the shared virtual MAC, so multicast CARP splits the brain. # Node-local settings are pushed by scripts/configure-ha.sh during apply. ha = optional(object({ backup_zone = string diff --git a/src/modules/debug-bastion/README.md b/src/modules/debug-bastion/README.md new file mode 100644 index 0000000..cdc1408 --- /dev/null +++ b/src/modules/debug-bastion/README.md @@ -0,0 +1,60 @@ + +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | + +## Providers + +| Name | Version | +| ---- | ------- | +| [stackit](#provider\_stackit) | >= 0.114.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| ---- | ---- | +| [stackit_key_pair.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/key_pair) | resource | +| [stackit_network_interface.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network_interface) | resource | +| [stackit_public_ip.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/public_ip) | resource | +| [stackit_security_group.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/security_group) | resource | +| [stackit_security_group_rule.ssh](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/security_group_rule) | resource | +| [stackit_server.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/server) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign a public IP to bastion network interface. | `bool` | n/a | yes | +| [availability\_zone](#input\_availability\_zone) | Availability zone for the bastion server. | `string` | `null` | no | +| [boot\_volume\_size](#input\_boot\_volume\_size) | Boot volume size in GB. | `number` | n/a | yes | +| [enabled](#input\_enabled) | Whether debug bastion resources should be created. | `bool` | n/a | yes | +| [image\_id](#input\_image\_id) | Image ID for the bastion boot volume. | `string` | n/a | yes | +| [install\_kubectl](#input\_install\_kubectl) | Whether to install kubectl via cloud-init. | `bool` | n/a | yes | +| [machine\_type](#input\_machine\_type) | Machine type for the bastion server. | `string` | n/a | yes | +| [name](#input\_name) | Bastion server name. | `string` | n/a | yes | +| [network\_id](#input\_network\_id) | SNA network ID for the bastion network interface. | `string` | n/a | yes | +| [project\_id](#input\_project\_id) | STACKIT project ID where bastion resources are created. | `string` | n/a | yes | +| [short\_prefix](#input\_short\_prefix) | Short naming prefix for key/security-group resources. | `string` | n/a | yes | +| [sna\_enabled](#input\_sna\_enabled) | Whether SNA networking is enabled for the cluster. | `bool` | n/a | yes | +| [ssh\_allowed\_cidrs](#input\_ssh\_allowed\_cidrs) | CIDRs allowed for SSH ingress. | `list(string)` | n/a | yes | +| [ssh\_public\_key](#input\_ssh\_public\_key) | Optional inline SSH public key. | `string` | `null` | no | +| [ssh\_public\_key\_path](#input\_ssh\_public\_key\_path) | Path to SSH public key file when inline key is not provided. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| [enabled](#output\_enabled) | Whether bastion resources are enabled. | +| [network\_interface\_id](#output\_network\_interface\_id) | Bastion network interface ID when enabled. | +| [public\_ip](#output\_public\_ip) | Bastion public IP when enabled and assign\_public\_ip=true. | +| [server\_id](#output\_server\_id) | Bastion server ID when enabled. | +| [ssh\_command](#output\_ssh\_command) | Ready-to-use SSH command when public IP is assigned. | +| [ssh\_user](#output\_ssh\_user) | Default SSH user for bastion access. | + diff --git a/src/modules/debug-bastion/main.tf b/src/modules/debug-bastion/main.tf index ddc865b..eee34a5 100644 --- a/src/modules/debug-bastion/main.tf +++ b/src/modules/debug-bastion/main.tf @@ -1,3 +1,7 @@ +################## +## BASTION HOST ## +################## + locals { ssh_public_key = var.enabled ? try( trimspace(var.ssh_public_key), @@ -21,6 +25,10 @@ EOT ) : null } +############## +## KEY PAIR ## +############## + resource "stackit_key_pair" "this" { count = var.enabled ? 1 : 0 @@ -40,6 +48,10 @@ resource "stackit_key_pair" "this" { } } +#################### +## SECURITY GROUP ## +#################### + resource "stackit_security_group" "this" { count = var.enabled ? 1 : 0 @@ -70,6 +82,10 @@ resource "stackit_security_group_rule" "ssh" { } } +####################### +## NETWORK INTERFACE ## +####################### + resource "stackit_network_interface" "this" { count = var.enabled ? 1 : 0 @@ -80,6 +96,10 @@ resource "stackit_network_interface" "this" { security_group_ids = [stackit_security_group.this[0].security_group_id] } +############ +## SERVER ## +############ + resource "stackit_server" "this" { count = var.enabled ? 1 : 0 @@ -101,6 +121,10 @@ resource "stackit_server" "this" { user_data = local.user_data } +############### +## PUBLIC IP ## +############### + resource "stackit_public_ip" "this" { count = var.enabled && var.assign_public_ip ? 1 : 0 diff --git a/src/modules/debug-bastion/terraform.tf b/src/modules/debug-bastion/terraform.tf index 3ebb3ce..385a232 100644 --- a/src/modules/debug-bastion/terraform.tf +++ b/src/modules/debug-bastion/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } } } diff --git a/src/modules/devops/1-project.tf b/src/modules/devops/1-project.tf index 8472290..d16e004 100644 --- a/src/modules/devops/1-project.tf +++ b/src/modules/devops/1-project.tf @@ -22,4 +22,4 @@ resource "stackit_authorization_project_role_assignment" "this" { resource_id = stackit_resourcemanager_project.this.project_id role = each.value.role subject = each.value.subject -} \ No newline at end of file +} diff --git a/src/modules/devops/2-git.tf b/src/modules/devops/2-git.tf index 64cf326..f63940f 100644 --- a/src/modules/devops/2-git.tf +++ b/src/modules/devops/2-git.tf @@ -9,4 +9,4 @@ resource "stackit_git" "git" { name = replace(lower(substr(var.company_name, 0, 31)), " ", "-") acl = var.allowed_network_ranges flavor = var.git_flavor -} \ No newline at end of file +} diff --git a/src/modules/devops/README.md b/src/modules/devops/README.md index 6577001..d5e96ed 100644 --- a/src/modules/devops/README.md +++ b/src/modules/devops/README.md @@ -2,14 +2,14 @@ ## Requirements | Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.93.0 | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | ## Providers | Name | Version | -|------|---------| +| ---- | ------- | | [stackit](#provider\_stackit) | 0.88.0 | ## Modules @@ -19,7 +19,7 @@ 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_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 | @@ -27,7 +27,7 @@ No modules. ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [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 | @@ -42,8 +42,8 @@ No modules. ## 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. | - \ No newline at end of file + diff --git a/src/modules/devops/terraform.tf b/src/modules/devops/terraform.tf index 233ba67..385a232 100644 --- a/src/modules/devops/terraform.tf +++ b/src/modules/devops/terraform.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } } -} \ No newline at end of file +} diff --git a/src/modules/devops/variables.tf b/src/modules/devops/variables.tf index 87deb26..f5ba5f7 100644 --- a/src/modules/devops/variables.tf +++ b/src/modules/devops/variables.tf @@ -60,4 +60,4 @@ variable "role_assignments" { })) 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/src/modules/firewall-config/5-ha-sync.tf b/src/modules/firewall-config/5-ha-sync.tf index 7a62283..fe544b0 100644 --- a/src/modules/firewall-config/5-ha-sync.tf +++ b/src/modules/firewall-config/5-ha-sync.tf @@ -5,9 +5,8 @@ # Replicates this policy to the HA peer after every change. OPNsense's XMLRPC config # sync only fires on GUI saves; configuration written through the REST API is never # pushed on its own, so without this step the backup node runs with an empty ruleset -# and black-holes traffic the moment it becomes CARP master (measured 2026-08-02: -# 86 s outage instead of ~1 s). The peer's own CARP VIP is excluded from the sync via -# its nosync flag, set by connectivity/scripts/configure-ha.sh. +# and black-holes traffic the moment it becomes CARP master. The peer's own CARP VIP is +# excluded from the sync via its nosync flag, set by connectivity/scripts/configure-ha.sh. resource "terraform_data" "ha_sync" { count = var.ha_sync ? 1 : 0 diff --git a/src/modules/firewall-config/README.md b/src/modules/firewall-config/README.md index f2d1577..3f4ce6e 100644 --- a/src/modules/firewall-config/README.md +++ b/src/modules/firewall-config/README.md @@ -157,8 +157,8 @@ The generic [`Mastercard/restapi`](https://registry.terraform.io/providers/Maste provider also drives this API, but OPNsense answers reads in an expanded form (every select field becomes an options object with `selected` flags) that never matches what was written. Suppressing the resulting permanent diff with `ignore_all_server_changes` also -suppresses diffs coming from the configuration, which makes objects write-once — measured: -adding a CIDR to an alias reported `No changes` while the appliance kept the old content. +suppresses diffs coming from the configuration, which makes objects write-once: adding a +CIDR to an alias reports `No changes` while the appliance keeps the old content. `browningluke/opnsense` maps the two forms internally, so in-place updates and drift detection work. It is community maintained and its author advises against production use. @@ -170,14 +170,15 @@ escape hatch for that specific object. | Name | Version | | ---- | ------- | -| [terraform](#requirement\_terraform) | >= 1.10 | -| [opnsense](#requirement\_opnsense) | 0.24.0 | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [opnsense](#requirement\_opnsense) | >= 0.26.0 | ## Providers | Name | Version | | ---- | ------- | | [opnsense](#provider\_opnsense) | 0.24.0 | +| [terraform](#provider\_terraform) | n/a | ## Modules @@ -187,19 +188,24 @@ No modules. | Name | Type | | ---- | ---- | -| [opnsense_firewall_alias.this](https://registry.terraform.io/providers/browningluke/opnsense/0.24.0/docs/resources/firewall_alias) | resource | -| [opnsense_firewall_category.this](https://registry.terraform.io/providers/browningluke/opnsense/0.24.0/docs/resources/firewall_category) | resource | -| [opnsense_firewall_filter.this](https://registry.terraform.io/providers/browningluke/opnsense/0.24.0/docs/resources/firewall_filter) | resource | -| [opnsense_firewall_nat.this](https://registry.terraform.io/providers/browningluke/opnsense/0.24.0/docs/resources/firewall_nat) | resource | -| [opnsense_firewall_nat_port_forward.this](https://registry.terraform.io/providers/browningluke/opnsense/0.24.0/docs/resources/firewall_nat_port_forward) | resource | -| [opnsense_route.this](https://registry.terraform.io/providers/browningluke/opnsense/0.24.0/docs/resources/route) | resource | +| [opnsense_firewall_alias.this](https://registry.terraform.io/providers/browningluke/opnsense/latest/docs/resources/firewall_alias) | resource | +| [opnsense_firewall_category.this](https://registry.terraform.io/providers/browningluke/opnsense/latest/docs/resources/firewall_category) | resource | +| [opnsense_firewall_filter.this](https://registry.terraform.io/providers/browningluke/opnsense/latest/docs/resources/firewall_filter) | resource | +| [opnsense_firewall_nat.this](https://registry.terraform.io/providers/browningluke/opnsense/latest/docs/resources/firewall_nat) | resource | +| [opnsense_firewall_nat_port_forward.this](https://registry.terraform.io/providers/browningluke/opnsense/latest/docs/resources/firewall_nat_port_forward) | resource | +| [opnsense_route.this](https://registry.terraform.io/providers/browningluke/opnsense/latest/docs/resources/route) | resource | +| [terraform_data.ha_sync](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | ## Inputs | Name | Description | Type | Default | Required | | ---- | ----------- | ---- | ------- | :------: | +| [admin\_password](#input\_admin\_password) | Password of admin\_username, used by the HA sync trigger. Defaults to the password baked into the STACKIT OPNsense image. Only used when ha\_sync is set. | `string` | `"STACKIT123!"` | no | +| [admin\_username](#input\_admin\_username) | Appliance login used by the HA sync trigger. Only used when ha\_sync is set. | `string` | `"root"` | no | | [aliases](#input\_aliases) | Firewall aliases keyed by alias name. Reference an alias from a rule, route or NAT entry by using its key wherever a network is expected. type = "host" also accepts FQDNs, which the appliance re-resolves periodically. |
map(object({
type = optional(string, "network")
enabled = optional(bool, true)
description = optional(string, null)
content = optional(list(string), [])
update_freq = optional(number, null)
stats = optional(bool, false)
}))
| `{}` | no | | [category\_name](#input\_category\_name) | OPNsense category every object created by this module is tagged with, so managed objects are recognisable in the GUI. | `string` | `"landing-zone"` | no | +| [endpoint](#input\_endpoint) | Base URL of the OPNsense API on the primary node, e.g. https://10.0.2.4. Only used by the HA sync trigger; the provider itself is configured in the root module. | `string` | `null` | no | +| [ha\_sync](#input\_ha\_sync) | Push this policy to the HA peer after every change (POST /api/core/hasync\_status/restart\_all on the primary). Required for the active/passive CARP pair: OPNsense's own config sync never fires on API writes, so without it the backup runs an empty ruleset. Enabled automatically when connectivity.firewall.ha is set. | `bool` | `false` | no | | [outbound\_nat](#input\_outbound\_nat) | Additional outbound NAT rules keyed by name. target\_ip accepts an address, an alias, or ip such as wanip. |
map(object({
sequence = optional(number, 200)
enabled = optional(bool, true)
interface = optional(string, "wan")
protocol = optional(string, "any")
ip_protocol = optional(string, "inet")
source_net = optional(string, "any")
destination_net = optional(string, "any")
target_ip = optional(string, "wanip")
disable_nat = optional(bool, false)
log = optional(bool, false)
description = optional(string, null)
}))
| `{}` | no | | [port\_forwards](#input\_port\_forwards) | Inbound port forwards from the internet keyed by name. Every entry punches a hole through the WAN, so keep the list short and set source\_net where possible. |
map(object({
sequence = optional(number, 100)
enabled = optional(bool, true)
interfaces = optional(list(string), ["wan"])
protocol = optional(string, "TCP")
ip_protocol = optional(string, "inet")
source_net = optional(string, "any")
destination_net = optional(string, "wanip")
destination_port = string
target_ip = string
target_port = optional(string, null)
nat_reflection = optional(string, "default")
log = optional(bool, true)
description = optional(string, null)
}))
| `{}` | no | | [routes](#input\_routes) | Static routes keyed by name. gateway must name a gateway that exists on the appliance; the STACKIT image ships LAN\_DHCP and WAN\_DHCP. |
map(object({
enabled = optional(bool, true)
network = string
gateway = string
description = optional(string, null)
}))
| `{}` | no | diff --git a/src/modules/firewall-config/scripts/sync-ha-peer.sh b/src/modules/firewall-config/scripts/sync-ha-peer.sh index 4c7f142..0c7c417 100644 --- a/src/modules/firewall-config/scripts/sync-ha-peer.sh +++ b/src/modules/firewall-config/scripts/sync-ha-peer.sh @@ -5,11 +5,10 @@ # the hood (verified against the stable/26.1 HasyncStatusController source). This is the # API twin of the GUI's "Synchronize and reconfigure all" button. # -# Needed because OPNsense's XMLRPC config sync only fires on GUI saves — configuration +# Needed because OPNsense's XMLRPC config sync only fires on GUI saves. Configuration # written through the REST API (i.e. everything OpenTofu does) is never replicated on -# its own (measured 2026-08-02: policy applied via API, peer stayed empty until this -# endpoint was called). The peer's CARP VIP survives the sync because configure-ha.sh -# creates it with nosync=1. +# its own. The peer's CARP VIP survives the sync because configure-ha.sh creates it +# with nosync=1. # # Authenticates with the admin login through a GUI session cookie + CSRF token, like # bootstrap-api-key.sh. diff --git a/src/modules/firewall-config/terraform.tf b/src/modules/firewall-config/terraform.tf index 5dc64d1..dbf26f5 100644 --- a/src/modules/firewall-config/terraform.tf +++ b/src/modules/firewall-config/terraform.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { opnsense = { source = "browningluke/opnsense" - version = "0.24.0" + version = ">= 0.26.0" } } } diff --git a/src/modules/governance/1-rm-folders.tf b/src/modules/governance/1-rm-folders.tf index 186422c..ce82ad8 100644 --- a/src/modules/governance/1-rm-folders.tf +++ b/src/modules/governance/1-rm-folders.tf @@ -32,10 +32,9 @@ resource "stackit_resourcemanager_folder" "this" { name = each.value.name parent_container_id = var.rm_folder_parent_id != null ? var.rm_folder_parent_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 lifecycle { - ignore_changes = [labels] + ignore_changes = [labels] # provider bug: empty map becomes null after apply } } @@ -57,4 +56,4 @@ resource "stackit_authorization_folder_role_assignment" "readers" { resource_id = stackit_resourcemanager_folder.this[each.value.folder_key].folder_id role = "auditor" subject = each.value.subject -} \ No newline at end of file +} diff --git a/src/modules/governance/2-custom-roles.tf b/src/modules/governance/2-custom-roles.tf index 0b3b77e..4f1a231 100644 --- a/src/modules/governance/2-custom-roles.tf +++ b/src/modules/governance/2-custom-roles.tf @@ -9,4 +9,4 @@ resource "stackit_authorization_project_custom_role" "this" { name = each.value.name description = each.value.description permissions = each.value.permissions -} \ No newline at end of file +} diff --git a/src/modules/governance/3-organization-roles.tf b/src/modules/governance/3-organization-roles.tf index 9b4807a..bf66dfe 100644 --- a/src/modules/governance/3-organization-roles.tf +++ b/src/modules/governance/3-organization-roles.tf @@ -16,4 +16,4 @@ resource "stackit_authorization_organization_role_assignment" "auditor" { resource_id = var.organization_id role = "organization.auditor" subject = each.value -} \ No newline at end of file +} diff --git a/src/modules/governance/README.md b/src/modules/governance/README.md index 516e3ab..e7d1ea2 100644 --- a/src/modules/governance/README.md +++ b/src/modules/governance/README.md @@ -2,15 +2,15 @@ ## Requirements | Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.93.0 | -| [time](#requirement\_time) | >= 0.13.0 | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | +| [time](#requirement\_time) | >= 0.14.1 | ## Providers | Name | Version | -|------|---------| +| ---- | ------- | | [stackit](#provider\_stackit) | 0.93.0 | ## Modules @@ -20,7 +20,7 @@ No modules. ## Resources | Name | Type | -|------|------| +| ---- | ---- | | [stackit_authorization_folder_role_assignment.owners](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_folder_role_assignment) | resource | | [stackit_authorization_folder_role_assignment.readers](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_folder_role_assignment) | resource | | [stackit_authorization_organization_role_assignment.auditor](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_organization_role_assignment) | resource | @@ -31,7 +31,7 @@ No modules. ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [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 | @@ -44,8 +44,8 @@ No modules. ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [custom\_role\_ids](#output\_custom\_role\_ids) | Map of custom role names to their role IDs | | [folder\_container\_ids](#output\_folder\_container\_ids) | Map of all folder keys to their container IDs for easy reference | | [organization\_role\_assignments](#output\_organization\_role\_assignments) | Map of organization-level role assignment subjects grouped by role | - \ No newline at end of file + diff --git a/src/modules/governance/outputs.tf b/src/modules/governance/outputs.tf index 529d63d..38e668d 100644 --- a/src/modules/governance/outputs.tf +++ b/src/modules/governance/outputs.tf @@ -17,4 +17,4 @@ output "organization_role_assignments" { owners = { for k, r in stackit_authorization_organization_role_assignment.owner : k => r.subject } auditors = { for k, r in stackit_authorization_organization_role_assignment.auditor : k => r.subject } } -} \ No newline at end of file +} diff --git a/src/modules/governance/terraform.tf b/src/modules/governance/terraform.tf index de61aad..fd3104a 100644 --- a/src/modules/governance/terraform.tf +++ b/src/modules/governance/terraform.tf @@ -1,14 +1,14 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } time = { source = "hashicorp/time" - version = "~> 0.14.0" + version = ">= 0.14.1" } } -} \ No newline at end of file +} diff --git a/src/modules/governance/variables.tf b/src/modules/governance/variables.tf index b98f8cb..7c43b40 100644 --- a/src/modules/governance/variables.tf +++ b/src/modules/governance/variables.tf @@ -71,4 +71,4 @@ variable "organization_owners" { variable "owner_email" { type = string description = "Email address of the owner for the folders. Required for STACKIT resource manager." -} \ No newline at end of file +} diff --git a/src/modules/landing-zone/1-project.tf b/src/modules/landing-zone/1-project.tf index e08652e..2b5ede8 100644 --- a/src/modules/landing-zone/1-project.tf +++ b/src/modules/landing-zone/1-project.tf @@ -21,4 +21,4 @@ resource "stackit_resourcemanager_project" "this" { labels ] } -} \ No newline at end of file +} diff --git a/src/modules/landing-zone/2-rbac.tf b/src/modules/landing-zone/2-rbac.tf index 4f597c8..2b2a1f2 100644 --- a/src/modules/landing-zone/2-rbac.tf +++ b/src/modules/landing-zone/2-rbac.tf @@ -29,4 +29,4 @@ resource "stackit_authorization_project_role_assignment" "sa_owner" { resource_id = stackit_resourcemanager_project.this.project_id role = "owner" subject = stackit_service_account.automation.email -} \ No newline at end of file +} diff --git a/src/modules/landing-zone/4-secrets-manager.tf b/src/modules/landing-zone/4-secrets-manager.tf index ea454bf..4c6b8c7 100644 --- a/src/modules/landing-zone/4-secrets-manager.tf +++ b/src/modules/landing-zone/4-secrets-manager.tf @@ -6,4 +6,4 @@ 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/src/modules/landing-zone/5-bucket.tf b/src/modules/landing-zone/5-bucket.tf index 1cc34f3..2df6377 100644 --- a/src/modules/landing-zone/5-bucket.tf +++ b/src/modules/landing-zone/5-bucket.tf @@ -29,4 +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 -} \ No newline at end of file +} diff --git a/src/modules/landing-zone/6-service-account.tf b/src/modules/landing-zone/6-service-account.tf index 58e2aef..cd2de92 100644 --- a/src/modules/landing-zone/6-service-account.tf +++ b/src/modules/landing-zone/6-service-account.tf @@ -19,4 +19,4 @@ resource "stackit_service_account_key" "automation" { rotate_when_changed = { rotation = time_rotating.key_rotate.id } -} \ No newline at end of file +} diff --git a/src/modules/landing-zone/README.md b/src/modules/landing-zone/README.md index eac95c2..1d79265 100644 --- a/src/modules/landing-zone/README.md +++ b/src/modules/landing-zone/README.md @@ -2,15 +2,15 @@ ## Requirements | Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.5 | -| [stackit](#requirement\_stackit) | >=0.93.0 | -| [time](#requirement\_time) | >=0.13.1 | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | +| [time](#requirement\_time) | >= 0.14.1 | ## Providers | Name | Version | -|------|---------| +| ---- | ------- | | [stackit](#provider\_stackit) | 0.93.0 | | [time](#provider\_time) | 0.13.1 | @@ -21,7 +21,7 @@ No modules. ## 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 | @@ -31,6 +31,7 @@ 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_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_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 | @@ -42,7 +43,7 @@ No modules. ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [corporate](#input\_corporate) | Whether this landing zone uses corporate networking (network area + routing). Set to false for public internet. | `bool` | `false` | no | | [custom\_roles](#input\_custom\_roles) | List of custom roles to create for the project. |
list(object({
name = string
description = string
permissions = list(string)
}))
| n/a | yes | | [dns\_zone\_name](#input\_dns\_zone\_name) | Full DNS zone domain name for this landing zone. Set to null to skip DNS zone creation. | `string` | `null` | no | @@ -52,6 +53,7 @@ No modules. | [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 | +| [observability](#input\_observability) | Optional observability instance configuration in the landing zone project. |
object({
enabled = optional(bool, false)
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
name = optional(string, 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 | @@ -62,12 +64,18 @@ No modules. ## 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'. | +| [observability\_grafana\_admin\_password](#output\_observability\_grafana\_admin\_password) | The Grafana admin password of the optional landing zone observability instance. | +| [observability\_grafana\_admin\_user](#output\_observability\_grafana\_admin\_user) | The Grafana admin username of the optional landing zone observability instance. | +| [observability\_grafana\_url](#output\_observability\_grafana\_url) | The Grafana URL of the optional landing zone observability instance. | +| [observability\_instance\_id](#output\_observability\_instance\_id) | The optional observability instance ID in the landing zone project. | +| [observability\_metrics\_push\_url](#output\_observability\_metrics\_push\_url) | The Prometheus remote-write URL of the optional landing zone observability instance. | | [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 +| [secretsmanager\_instance\_id](#output\_secretsmanager\_instance\_id) | The ID of the landing zone Secrets Manager instance. | + diff --git a/src/modules/landing-zone/outputs.tf b/src/modules/landing-zone/outputs.tf index ce79f72..e65e84f 100644 --- a/src/modules/landing-zone/outputs.tf +++ b/src/modules/landing-zone/outputs.tf @@ -62,4 +62,4 @@ output "observability_grafana_admin_password" { description = "The Grafana admin password of the optional landing zone observability instance." sensitive = true value = var.observability.enabled ? stackit_observability_instance.this[0].grafana_initial_admin_password : null -} \ No newline at end of file +} diff --git a/src/modules/landing-zone/terraform.tf b/src/modules/landing-zone/terraform.tf index 90cd055..fd3104a 100644 --- a/src/modules/landing-zone/terraform.tf +++ b/src/modules/landing-zone/terraform.tf @@ -1,14 +1,14 @@ terraform { - required_version = ">= 1.5" + required_version = ">= 1.11" required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } time = { source = "hashicorp/time" - version = "~> 0.14.0" + version = ">= 0.14.1" } } -} \ No newline at end of file +} diff --git a/src/modules/landing-zone/variables.tf b/src/modules/landing-zone/variables.tf index 37a39be..7d75614 100644 --- a/src/modules/landing-zone/variables.tf +++ b/src/modules/landing-zone/variables.tf @@ -99,4 +99,4 @@ variable "observability" { }) description = "Optional observability instance configuration in the landing zone project." default = {} -} \ No newline at end of file +} diff --git a/src/modules/management/1-project.tf b/src/modules/management/1-project.tf index 8e6a60f..b5cb6c4 100644 --- a/src/modules/management/1-project.tf +++ b/src/modules/management/1-project.tf @@ -15,4 +15,4 @@ resource "stackit_authorization_project_role_assignment" "this" { resource_id = stackit_resourcemanager_project.this.project_id role = each.value.role subject = each.value.subject -} \ No newline at end of file +} diff --git a/src/modules/management/2-secrets-manager.tf b/src/modules/management/2-secrets-manager.tf index 342816e..7f433bd 100644 --- a/src/modules/management/2-secrets-manager.tf +++ b/src/modules/management/2-secrets-manager.tf @@ -5,7 +5,6 @@ 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 } resource "stackit_secretsmanager_user" "default" { @@ -13,4 +12,4 @@ resource "stackit_secretsmanager_user" "default" { 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/src/modules/management/3-object-storage.tf b/src/modules/management/3-object-storage.tf index 305718c..f5a7c7d 100644 --- a/src/modules/management/3-object-storage.tf +++ b/src/modules/management/3-object-storage.tf @@ -81,4 +81,4 @@ resource "vault_kv_secret_v2" "object_storage_credentials" { SECRET_ACCESS_KEY = stackit_objectstorage_credential.this.secret_access_key } ) -} \ No newline at end of file +} diff --git a/src/modules/management/4-service-account.tf b/src/modules/management/4-service-account.tf index 01783e0..166e85d 100644 --- a/src/modules/management/4-service-account.tf +++ b/src/modules/management/4-service-account.tf @@ -48,4 +48,4 @@ resource "stackit_service_account_federated_identity_provider" "this" { issuer = each.value.issuer assertions = each.value.assertions -} \ No newline at end of file +} diff --git a/src/modules/management/5-observability.tf b/src/modules/management/5-observability.tf index 9e54ebc..b5ee7cf 100644 --- a/src/modules/management/5-observability.tf +++ b/src/modules/management/5-observability.tf @@ -37,4 +37,4 @@ resource "vault_kv_secret_v2" "observability" { PASSWORD = stackit_observability_credential.this[0].password } ) -} \ No newline at end of file +} diff --git a/src/modules/management/README.md b/src/modules/management/README.md index 8d84a8c..aa80f9b 100644 --- a/src/modules/management/README.md +++ b/src/modules/management/README.md @@ -3,10 +3,10 @@ | Name | Version | | ---- | ------- | -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | 0.106.0 | -| [time](#requirement\_time) | ~> 0.14.0 | -| [vault](#requirement\_vault) | 5.10.1 | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | +| [time](#requirement\_time) | >= 0.14.1 | +| [vault](#requirement\_vault) | >= 5.11.0 | ## Providers @@ -24,42 +24,42 @@ No modules. | Name | Type | | ---- | ---- | -| [stackit_authorization_organization_role_assignment.sa_owner](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/authorization_organization_role_assignment) | resource | -| [stackit_authorization_project_role_assignment.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/authorization_project_role_assignment) | resource | -| [stackit_logs_access_token.audit_read](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/logs_access_token) | resource | -| [stackit_logs_access_token.audit_write](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/logs_access_token) | resource | -| [stackit_logs_instance.audit](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/logs_instance) | resource | -| [stackit_objectstorage_bucket.audit_logs](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/objectstorage_bucket) | resource | -| [stackit_objectstorage_bucket.default](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/objectstorage_bucket) | resource | -| [stackit_objectstorage_bucket.tfstate](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/objectstorage_bucket) | resource | -| [stackit_objectstorage_compliance_lock.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/objectstorage_compliance_lock) | resource | -| [stackit_objectstorage_credential.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/objectstorage_credential) | resource | -| [stackit_objectstorage_credentials_group.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/objectstorage_credentials_group) | resource | -| [stackit_objectstorage_default_retention.audit_logs](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/objectstorage_default_retention) | resource | -| [stackit_observability_credential.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/observability_credential) | resource | -| [stackit_observability_instance.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/observability_instance) | resource | -| [stackit_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/resourcemanager_project) | resource | -| [stackit_secretsmanager_instance.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/secretsmanager_instance) | resource | -| [stackit_secretsmanager_user.default](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/secretsmanager_user) | resource | -| [stackit_service_account.automation](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/service_account) | resource | -| [stackit_service_account_federated_identity_provider.this](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/service_account_federated_identity_provider) | resource | -| [stackit_service_account_key.automation](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/service_account_key) | resource | -| [stackit_telemetrylink.audit](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/telemetrylink) | resource | -| [stackit_telemetryrouter_access_token.audit_link](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/telemetryrouter_access_token) | resource | -| [stackit_telemetryrouter_destination.audit_archive](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/telemetryrouter_destination) | resource | -| [stackit_telemetryrouter_destination.audit_logs](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/telemetryrouter_destination) | resource | -| [stackit_telemetryrouter_instance.audit](https://registry.terraform.io/providers/stackitcloud/stackit/0.106.0/docs/resources/telemetryrouter_instance) | resource | +| [stackit_authorization_organization_role_assignment.sa_owner](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_organization_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_logs_access_token.audit_read](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/logs_access_token) | resource | +| [stackit_logs_access_token.audit_write](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/logs_access_token) | resource | +| [stackit_logs_instance.audit](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/logs_instance) | resource | +| [stackit_objectstorage_bucket.audit_logs](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_bucket) | 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_compliance_lock.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_compliance_lock) | 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_objectstorage_default_retention.audit_logs](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/objectstorage_default_retention) | 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_federated_identity_provider.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/service_account_federated_identity_provider) | resource | +| [stackit_service_account_key.automation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/service_account_key) | resource | +| [stackit_telemetrylink.audit](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/telemetrylink) | resource | +| [stackit_telemetryrouter_access_token.audit_link](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/telemetryrouter_access_token) | resource | +| [stackit_telemetryrouter_destination.audit_archive](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/telemetryrouter_destination) | resource | +| [stackit_telemetryrouter_destination.audit_logs](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/telemetryrouter_destination) | resource | +| [stackit_telemetryrouter_instance.audit](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/telemetryrouter_instance) | resource | | [time_rotating.automation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/rotating) | resource | -| [vault_kv_secret_v2.audit_logs](https://registry.terraform.io/providers/hashicorp/vault/5.10.1/docs/resources/kv_secret_v2) | resource | -| [vault_kv_secret_v2.object_storage_credentials](https://registry.terraform.io/providers/hashicorp/vault/5.10.1/docs/resources/kv_secret_v2) | resource | -| [vault_kv_secret_v2.observability](https://registry.terraform.io/providers/hashicorp/vault/5.10.1/docs/resources/kv_secret_v2) | resource | -| [vault_kv_secret_v2.service_account_key_automation](https://registry.terraform.io/providers/hashicorp/vault/5.10.1/docs/resources/kv_secret_v2) | resource | +| [vault_kv_secret_v2.audit_logs](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/kv_secret_v2) | 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 | Name | Description | Type | Default | Required | | ---- | ----------- | ---- | ------- | :------: | -| [audit\_logs](#input\_audit\_logs) | Audit logs configuration. The router forwards to two destinations: OTLP into the Logs instance for querying, and S3 into the audit bucket for long-term archive. retention\_days applies to both, driving the Logs instance retention and, when s3\_object\_lock is enabled, the archive bucket's default retention. link\_scopes defaults to a single organization-wide link; set it to attach individual folders or projects instead. |
object({
retention_days = optional(number, 30)
acl = optional(list(string), null)
s3_object_lock = optional(bool, false)
link_scopes = optional(list(object({
resource_type = string # organization, folder, project
resource_id = string
})), null)
})
| `null` | no | +| [audit\_logs](#input\_audit\_logs) | Audit logs configuration. The router forwards to two destinations: OTLP into the Logs instance for querying, and S3 into the audit bucket for long-term archive. retention\_days applies to both, driving the Logs instance retention and, when s3\_object\_lock is enabled, the archive bucket's default retention. link\_scopes defaults to a single organization-wide link; set it to attach individual folders or projects instead. |
object({
retention_days = optional(number, 180)
acl = optional(list(string), null)
s3_object_lock = optional(bool, true)
link_scopes = optional(list(object({
resource_type = string # organization, folder, project
resource_id = string
})), null)
})
| `null` | no | | [federated\_identity\_providers](#input\_federated\_identity\_providers) | List of federated identity providers to configure for the management service account. |
list(object({
name = string
issuer = string
assertions = list(object({
item = string
operator = string
value = string
}))
}))
| `[]` | 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 | @@ -86,4 +86,4 @@ No modules. | [secretsmanager\_password](#output\_secretsmanager\_password) | The password of the default Secrets Manager user. | | [secretsmanager\_username](#output\_secretsmanager\_username) | The username of the default Secrets Manager user. | | [service\_account\_email](#output\_service\_account\_email) | The email of the created service account. | - \ No newline at end of file + diff --git a/src/modules/management/terraform.tf b/src/modules/management/terraform.tf index e7ee7cd..00d9ca5 100644 --- a/src/modules/management/terraform.tf +++ b/src/modules/management/terraform.tf @@ -1,18 +1,18 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } time = { source = "hashicorp/time" - version = "~> 0.14.0" + version = ">= 0.14.1" } vault = { source = "hashicorp/vault" - version = "5.10.1" + version = ">= 5.11.0" } } -} \ No newline at end of file +} diff --git a/src/modules/management/variables.tf b/src/modules/management/variables.tf index 9d8361f..8c527d0 100644 --- a/src/modules/management/variables.tf +++ b/src/modules/management/variables.tf @@ -92,4 +92,4 @@ variable "audit_logs" { condition = try(!var.audit_logs.s3_object_lock || (var.audit_logs.retention_days >= 1 && var.audit_logs.retention_days <= 365), true) error_message = "audit_logs.retention_days must be between 1 and 365 when s3_object_lock is enabled, since that is the STACKIT object storage maximum." } -} \ No newline at end of file +} diff --git a/src/modules/namespace-service-demo/README.md b/src/modules/namespace-service-demo/README.md new file mode 100644 index 0000000..276c294 --- /dev/null +++ b/src/modules/namespace-service-demo/README.md @@ -0,0 +1,46 @@ + +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [grafana](#requirement\_grafana) | >= 4.45.2 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | + +## Providers + +| Name | Version | +| ---- | ------- | +| [grafana.observability](#provider\_grafana.observability) | >= 4.45.2 | +| [stackit](#provider\_stackit) | >= 0.114.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| ---- | ---- | +| [grafana_dashboard.namespace_overview](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/dashboard) | resource | +| [grafana_data_source.platform_prometheus](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source) | resource | +| [grafana_folder.stackit_managed](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/folder) | resource | +| [stackit_observability_credential.platform_metrics_reader](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/observability_credential) | resource | +| [stackit_observability_scrapeconfig.namespace_demo_ingestion](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/observability_scrapeconfig) | resource | +| [stackit_secretsmanager_user.external_secret_demo](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/secretsmanager_user) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [dashboard\_folder\_title](#input\_dashboard\_folder\_title) | Folder title used for managed landing-zone demo dashboards. | `string` | `"STACKIT Managed Dashboards"` | no | +| [dashboard\_passwords](#input\_dashboard\_passwords) | Grafana admin passwords keyed by landing-zone key for dashboard demo imports. | `map(string)` | `{}` | no | +| [services](#input\_services) | Enabled namespace-service demo configurations keyed by landing-zone key. |
map(object({
namespace = string
use_secretsmanager = bool
landing_zone_project_id = string
secretsmanager_instance_id = string
observability_instance_id = optional(string)
observability_grafana_url = optional(string)
observability_admin_user = optional(string)
demo_metrics_ingestion_enabled = optional(bool, false)
demo_metrics_ingestion_target_urls = optional(list(string), [])
demo_metrics_ingestion_scheme = optional(string, "https")
demo_metrics_ingestion_metrics_path = optional(string, "/")
demo_metrics_ingestion_scrape_interval = optional(string, "60s")
demo_metrics_ingestion_scrape_timeout = optional(string, "30s")
platform_project_id = optional(string)
platform_observability_instance_id = optional(string)
platform_observability_targets_url = optional(string)
dns_zone_name = optional(string)
}))
| `{}` | no | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| [samples](#output\_samples) | Demo sample references for enabled namespace-service demos. | +| [secret\_access](#output\_secret\_access) | Credentials for demo Secrets Manager users keyed by landing zone. | + diff --git a/src/modules/namespace-service-demo/main.tf b/src/modules/namespace-service-demo/main.tf index f69e0ff..13b9461 100644 --- a/src/modules/namespace-service-demo/main.tf +++ b/src/modules/namespace-service-demo/main.tf @@ -1,17 +1,6 @@ -terraform { - required_version = ">= 1.11" - - required_providers { - stackit = { - source = "stackitcloud/stackit" - version = "0.106.0" - } - grafana = { - source = "grafana/grafana" - version = "4.42.0" - } - } -} +############################ +## NAMESPACE SERVICE DEMO ## +############################ locals { services_with_secrets = { @@ -66,6 +55,10 @@ provider "grafana" { auth = "${local.grafana_provider_user}:${nonsensitive(local.grafana_provider_pass)}" } +##################### +## SECRETS MANAGER ## +##################### + resource "stackit_secretsmanager_user" "external_secret_demo" { for_each = local.services_with_secrets @@ -75,6 +68,10 @@ resource "stackit_secretsmanager_user" "external_secret_demo" { write_enabled = true } +################### +## OBSERVABILITY ## +################### + resource "stackit_observability_credential" "platform_metrics_reader" { for_each = local.services_with_observability @@ -109,6 +106,10 @@ resource "stackit_observability_scrapeconfig" "namespace_demo_ingestion" { ] } +############# +## GRAFANA ## +############# + resource "grafana_folder" "stackit_managed" { provider = grafana.observability diff --git a/src/modules/namespace-service-demo/terraform.tf b/src/modules/namespace-service-demo/terraform.tf new file mode 100644 index 0000000..5091213 --- /dev/null +++ b/src/modules/namespace-service-demo/terraform.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.11" + + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.114.0" + } + grafana = { + source = "grafana/grafana" + version = ">= 4.45.2" + } + } +} diff --git a/src/modules/platform-kubernetes/1-project.tf b/src/modules/platform-kubernetes/1-project.tf index 8f4d560..e76c93b 100644 --- a/src/modules/platform-kubernetes/1-project.tf +++ b/src/modules/platform-kubernetes/1-project.tf @@ -1,3 +1,7 @@ +############# +## PROJECT ## +############# + locals { project_labels = merge( { "region" = var.region }, diff --git a/src/modules/platform-kubernetes/2-network.tf b/src/modules/platform-kubernetes/2-network.tf index acab6e5..561585f 100644 --- a/src/modules/platform-kubernetes/2-network.tf +++ b/src/modules/platform-kubernetes/2-network.tf @@ -1,3 +1,7 @@ +############## +## DNS ZONE ## +############## + locals { dns_extension_zones = distinct(compact(var.dns.zones)) } @@ -11,6 +15,10 @@ resource "stackit_dns_zone" "ske_extension" { contact_email = var.owner_email } +############################# +## NETWORK AREA MEMBERSHIP ## +############################# + resource "time_sleep" "wait_for_network_area_membership" { count = var.network.sna_enabled ? 1 : 0 @@ -20,6 +28,10 @@ resource "time_sleep" "wait_for_network_area_membership" { depends_on = [stackit_resourcemanager_project.this] } +############# +## ROUTING ## +############# + resource "stackit_routing_table" "sna_egress" { count = var.network.sna_enabled && var.network.sna_network_area_id != null && var.network.firewall_next_hop_ip != null ? 1 : 0 @@ -51,6 +63,10 @@ resource "stackit_routing_table_route" "sna_default_route" { labels = local.project_labels } +############# +## NETWORK ## +############# + resource "stackit_network" "sna" { count = var.network.sna_enabled ? 1 : 0 @@ -59,4 +75,4 @@ resource "stackit_network" "sna" { ipv4_prefix_length = var.network.sna_network_prefix_length routed = true routing_table_id = var.network.firewall_next_hop_ip != null ? stackit_routing_table.sna_egress[0].routing_table_id : null -} \ No newline at end of file +} diff --git a/src/modules/platform-kubernetes/2-observability.tf b/src/modules/platform-kubernetes/2-observability.tf index b886bd0..ec4592b 100644 --- a/src/modules/platform-kubernetes/2-observability.tf +++ b/src/modules/platform-kubernetes/2-observability.tf @@ -1,3 +1,7 @@ +################### +## OBSERVABILITY ## +################### + resource "stackit_observability_instance" "this" { count = var.observability.enabled ? 1 : 0 diff --git a/src/modules/platform-kubernetes/3-cluster.tf b/src/modules/platform-kubernetes/3-cluster.tf index 319c4fd..5d466ee 100644 --- a/src/modules/platform-kubernetes/3-cluster.tf +++ b/src/modules/platform-kubernetes/3-cluster.tf @@ -1,3 +1,7 @@ +################# +## SKE CLUSTER ## +################# + locals { effective_dns_zones = var.dns.create_zones ? sort([ for zone in values(stackit_dns_zone.ske_extension) : zone.dns_name diff --git a/src/modules/platform-kubernetes/4-encrypted-volumes.tf b/src/modules/platform-kubernetes/4-encrypted-volumes.tf index 425e73b..98887bc 100644 --- a/src/modules/platform-kubernetes/4-encrypted-volumes.tf +++ b/src/modules/platform-kubernetes/4-encrypted-volumes.tf @@ -1,3 +1,7 @@ +################################## +## SKE INTERNAL SERVICE ACCOUNT ## +################################## + data "stackit_service_accounts" "ske_internal" { count = var.encrypted_volumes.enabled ? 1 : 0 @@ -7,6 +11,10 @@ data "stackit_service_accounts" "ske_internal" { depends_on = [stackit_ske_cluster.this] } +################# +## KMS KEYRING ## +################# + resource "stackit_kms_keyring" "this" { count = var.encrypted_volumes.enabled ? 1 : 0 @@ -25,6 +33,10 @@ resource "stackit_kms_key" "this" { purpose = "symmetric_encrypt_decrypt" } +######################### +## KMS SERVICE ACCOUNT ## +######################### + resource "stackit_service_account" "kms_manager" { count = var.encrypted_volumes.enabled ? 1 : 0 diff --git a/src/modules/platform-kubernetes/5-debug-bastion.tf b/src/modules/platform-kubernetes/5-debug-bastion.tf index a361fd9..58a0a35 100644 --- a/src/modules/platform-kubernetes/5-debug-bastion.tf +++ b/src/modules/platform-kubernetes/5-debug-bastion.tf @@ -1,3 +1,7 @@ +################### +## DEBUG BASTION ## +################### + locals { debug_bastion_enabled = var.debug_bastion.enabled && var.network.sna_enabled debug_bastion_short_prefix = trim(replace(substr(var.naming_pattern, 0, 14), "/-{2,}/", "-"), "-") diff --git a/src/modules/platform-kubernetes/README.md b/src/modules/platform-kubernetes/README.md index 0b66553..3336bf1 100644 --- a/src/modules/platform-kubernetes/README.md +++ b/src/modules/platform-kubernetes/README.md @@ -1,16 +1,78 @@ +## Requirements -## Platform Kubernetes Module +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | +| [time](#requirement\_time) | >= 0.14.1 | -This module provisions a central, region-scoped platform Kubernetes foundation: +## Providers -- Dedicated platform project -- SKE cluster with SNA/public network mode support -- HA baseline defaults: two node pools across two AZs with minimum two nodes per pool -- Optional central observability extension wiring -- Optional DNS extension wiring -- Optional encrypted volume foundation via KMS and Act-As IAM wiring +| Name | Version | +| ---- | ------- | +| [stackit](#provider\_stackit) | >= 0.114.0 | +| [time](#provider\_time) | >= 0.14.1 | -Run tfdocs/pre-commit hooks to regenerate full inputs/outputs documentation. +## Modules +| Name | Source | Version | +| ---- | ------ | ------- | +| [debug\_bastion](#module\_debug\_bastion) | ../debug-bastion | n/a | + +## Resources + +| Name | Type | +| ---- | ---- | +| [stackit_authorization_project_role_assignment.kms_admin](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_authorization_service_account_role_assignment.ske_impersonation](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/authorization_service_account_role_assignment) | resource | +| [stackit_dns_zone.ske_extension](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/dns_zone) | resource | +| [stackit_kms_key.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/kms_key) | resource | +| [stackit_kms_keyring.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/kms_keyring) | resource | +| [stackit_network.sna](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/network) | 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_routing_table.sna_egress](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/routing_table) | resource | +| [stackit_routing_table_route.sna_default_route](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/routing_table_route) | resource | +| [stackit_service_account.kms_manager](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/service_account) | resource | +| [stackit_ske_cluster.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/ske_cluster) | resource | +| [stackit_ske_kubeconfig.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/ske_kubeconfig) | resource | +| [time_sleep.wait_for_network_area_membership](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [stackit_service_accounts.ske_internal](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/data-sources/service_accounts) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [cluster](#input\_cluster) | SKE cluster configuration. |
object({
name = string
kubernetes_version_min = optional(string, null)
node_pools = optional(list(object({
name = string
machine_type = string
minimum = number
maximum = number
availability_zones = list(string)
allow_system_components = optional(bool, false)
volume_size = optional(number, 20)
volume_type = optional(string, "storage_premium_perf1")
os_name = optional(string, "flatcar")
labels = optional(map(string), {})
})), [
{
name = "system"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-1"]
allow_system_components = true
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "system"
}
},
{
name = "application"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-2"]
allow_system_components = false
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "application"
}
}
])
maintenance = optional(object({
enable_kubernetes_version_updates = optional(bool, true)
enable_machine_image_version_updates = optional(bool, true)
start = optional(string, "01:00:00Z")
end = optional(string, "02:00:00Z")
}), {})
})
| n/a | yes | +| [debug\_bastion](#input\_debug\_bastion) | Optional debug bastion VM in the SNA network with SSH access to test SKE connectivity from inside the private network. |
object({
enabled = optional(bool, false)
name = optional(string, null)
availability_zone = optional(string, null)
machine_type = optional(string, "g2i.1")
image_id = optional(string, "7b10e105-295b-4369-b6e0-567ec940a02b")
boot_volume_size = optional(number, 20)
ssh_public_key = optional(string, null)
ssh_public_key_path = optional(string, "~/.ssh/id_rsa.pub")
ssh_allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
assign_public_ip = optional(bool, true)
install_kubectl = optional(bool, true)
})
| `{}` | no | +| [dns](#input\_dns) | SKE DNS extension configuration. If create\_zones is true, zones are created in the platform project before cluster creation. |
object({
enabled = optional(bool, true)
create_zones = optional(bool, true)
zones = optional(list(string), [])
})
| `{}` | no | +| [encrypted\_volumes](#input\_encrypted\_volumes) | Optional encrypted volume setup for SKE via KMS and Kubernetes storage class. |
object({
enabled = optional(bool, false)
storage_class_name = optional(string, "stackit-encrypted-premium")
kms_keyring_name = optional(string, "ske-volume-keyring")
kms_key_name = optional(string, "ske-volume-key")
kms_key_version = optional(string, "1")
})
| `{}` | no | +| [labels](#input\_labels) | Additional labels to apply to resources in this module. | `map(string)` | `{}` | no | +| [naming\_pattern](#input\_naming\_pattern) | Naming prefix for resources in this module, e.g. myco-pltfm-k8s-eu01. | `string` | n/a | yes | +| [network](#input\_network) | Network settings for SKE. Set sna\_enabled=true and provide sna\_network\_area\_id for SNA; otherwise the cluster runs in public control-plane mode. |
object({
sna_enabled = optional(bool, false)
sna_network_area_id = optional(string, null)
firewall_next_hop_ip = optional(string, null)
sna_network_prefix_length = optional(number, 24)
})
| `{}` | no | +| [observability](#input\_observability) | Observability configuration for central cluster monitoring in the same project as the cluster. |
object({
enabled = optional(bool, true)
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
name = optional(string, null)
})
| `{}` | no | +| [organization\_id](#input\_organization\_id) | Organization ID used for routing table resources in network-area scope. | `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 | +| [region](#input\_region) | STACKIT region for the SKE cluster. | `string` | n/a | yes | +| [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 | +| ---- | ----------- | +| [debug\_bastion](#output\_debug\_bastion) | Debug bastion metadata when enabled for private cluster troubleshooting. | +| [dns\_extension\_zones](#output\_dns\_extension\_zones) | DNS zones configured for SKE DNS extension. | +| [encrypted\_volume\_support](#output\_encrypted\_volume\_support) | Configuration values for encrypted SKE volumes when enabled. | +| [kube\_config](#output\_kube\_config) | Kubeconfig for the created SKE cluster. | +| [observability\_instance\_id](#output\_observability\_instance\_id) | The observability instance ID used for cluster extension. | +| [observability\_targets\_url](#output\_observability\_targets\_url) | The Prometheus query endpoint URL of the optional platform observability instance. | +| [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. | +| [ske\_cluster\_name](#output\_ske\_cluster\_name) | The name of the created SKE cluster. | +| [ske\_cluster\_region](#output\_ske\_cluster\_region) | The region of the created SKE cluster. | diff --git a/src/modules/platform-kubernetes/terraform.tf b/src/modules/platform-kubernetes/terraform.tf index 99abeb8..fd3104a 100644 --- a/src/modules/platform-kubernetes/terraform.tf +++ b/src/modules/platform-kubernetes/terraform.tf @@ -1,14 +1,14 @@ terraform { - required_version = ">= 1.10, < 2.0" + required_version = ">= 1.11" required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } time = { source = "hashicorp/time" - version = "~> 0.14.0" + version = ">= 0.14.1" } } } diff --git a/src/modules/sandboxes/README.md b/src/modules/sandboxes/README.md index 9f87852..f7604f8 100644 --- a/src/modules/sandboxes/README.md +++ b/src/modules/sandboxes/README.md @@ -2,14 +2,14 @@ ## Requirements | Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | -| [stackit](#requirement\_stackit) | >=0.93.0 | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [stackit](#requirement\_stackit) | >= 0.114.0 | ## Providers | Name | Version | -|------|---------| +| ---- | ------- | | [stackit](#provider\_stackit) | 0.93.0 | ## Modules @@ -19,14 +19,14 @@ 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_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [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 | | [sandboxes](#input\_sandboxes) | List of sandboxes to create. |
list(object({
project_name = string
owner_emails = optional(list(string))
project_owner_email = string
}))
| `[]` | no | @@ -34,6 +34,6 @@ No modules. ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [projects](#output\_projects) | The created STACKIT projects with their IDs, container IDs, and names. | - \ No newline at end of file + diff --git a/src/modules/sandboxes/main.tf b/src/modules/sandboxes/main.tf index a7146df..a9cecda 100644 --- a/src/modules/sandboxes/main.tf +++ b/src/modules/sandboxes/main.tf @@ -30,4 +30,4 @@ resource "stackit_authorization_project_role_assignment" "this" { resource_id = stackit_resourcemanager_project.this[each.value.project_name].project_id role = "owner" subject = each.value.email -} \ No newline at end of file +} diff --git a/src/modules/sandboxes/terraform.tf b/src/modules/sandboxes/terraform.tf index 233ba67..385a232 100644 --- a/src/modules/sandboxes/terraform.tf +++ b/src/modules/sandboxes/terraform.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = ">= 0.114.0" } } -} \ No newline at end of file +} diff --git a/src/modules/sandboxes/variables.tf b/src/modules/sandboxes/variables.tf index d76a849..55da6ab 100644 --- a/src/modules/sandboxes/variables.tf +++ b/src/modules/sandboxes/variables.tf @@ -17,4 +17,4 @@ variable "sandboxes" { description = "List of sandboxes to create." default = [] -} \ No newline at end of file +} diff --git a/src/providers.tf b/src/providers.tf index acb35a1..78aee42 100644 --- a/src/providers.tf +++ b/src/providers.tf @@ -73,4 +73,4 @@ provider "opnsense" { api_key = local.firewall_api_credentials.api_key api_secret = local.firewall_api_credentials.api_secret allow_insecure = try(var.firewall_config.insecure, true) -} \ No newline at end of file +} diff --git a/src/terraform.tf b/src/terraform.tf index 1ce1df2..3423337 100644 --- a/src/terraform.tf +++ b/src/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.114.0" } kubernetes = { source = "hashicorp/kubernetes" @@ -12,11 +12,11 @@ terraform { } helm = { source = "hashicorp/helm" - version = "3.2.0" + version = "3.3.0" } time = { source = "hashicorp/time" - version = "0.14.0" + version = "0.14.1" } random = { source = "hashicorp/random" @@ -24,15 +24,15 @@ terraform { } vault = { source = "hashicorp/vault" - version = "5.10.1" + version = "5.11.0" } grafana = { source = "grafana/grafana" - version = "4.42.0" + version = "4.45.2" } opnsense = { source = "browningluke/opnsense" - version = "0.24.0" + version = "0.26.0" } } -} \ No newline at end of file +} diff --git a/src/tests/hub_spoke.tftest.hcl b/src/tests/hub_spoke.tftest.hcl index 584a25b..c9b24af 100644 --- a/src/tests/hub_spoke.tftest.hcl +++ b/src/tests/hub_spoke.tftest.hcl @@ -2,7 +2,7 @@ variables { owner_email = "example@digits.schwarz" company_name = "Test Corp" company_code = "tst" - organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + organization_id = "00000000-0000-0000-0000-000000000000" region = "eu01" labels = { diff --git a/src/tests/hub_spoke_firewall.tftest.hcl b/src/tests/hub_spoke_firewall.tftest.hcl index e707cd2..1caf6e8 100644 --- a/src/tests/hub_spoke_firewall.tftest.hcl +++ b/src/tests/hub_spoke_firewall.tftest.hcl @@ -2,7 +2,7 @@ variables { owner_email = "example@digits.schwarz" company_name = "Test Corp" company_code = "tst" - organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + organization_id = "00000000-0000-0000-0000-000000000000" region = "eu01" labels = { diff --git a/src/tests/standalone.tftest.hcl b/src/tests/standalone.tftest.hcl index eb1f86c..3e8085b 100644 --- a/src/tests/standalone.tftest.hcl +++ b/src/tests/standalone.tftest.hcl @@ -2,7 +2,7 @@ variables { owner_email = "example@digits.schwarz" company_name = "Test Corp" company_code = "tst" - organization_id = "b76b54b6-f55d-41a1-b3c3-30252f8b97cc" + organization_id = "00000000-0000-0000-0000-000000000000" region = "eu01" labels = {