diff --git a/.github/workflows/default-ci.yaml b/.github/workflows/default-ci.yaml index 75355ad..7f9f84d 100644 --- a/.github/workflows/default-ci.yaml +++ b/.github/workflows/default-ci.yaml @@ -1,4 +1,4 @@ -name: "Professional Services CI" +name: "Default CI" on: push: @@ -17,6 +17,23 @@ jobs: - name: TruffleHog Scan uses: edplato/trufflehog-actions-scan@master + todo-check: + name: "Check for Open TODOs" + runs-on: ${{ github.server_url == 'https://github.com' && 'ubuntu-latest' || 'stackit-ubuntu-22' }} + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Search codebase + run: | + # Searches recursively (-r), showing line numbers (-n), ignoring binary files (-I) + # Excludes the .git directory to prevent false positives + if grep -rnIE "# ?TODO" --exclude-dir=.git --exclude-dir=.github .; then + echo "Error: TODOs found in the codebase. Please resolve them before merging." + exit 1 + fi + echo "No TODOs found. Proceeding." + pre-commit-checks: name: "Pre-Commit Hooks" runs-on: ${{ github.server_url == 'https://github.com' && 'ubuntu-latest' || 'stackit-ubuntu-22' }} diff --git a/.github/workflows/github-mirror-ci.yaml b/.github/workflows/github-mirror-ci.yaml index c596648..de61ea9 100644 --- a/.github/workflows/github-mirror-ci.yaml +++ b/.github/workflows/github-mirror-ci.yaml @@ -28,11 +28,11 @@ jobs: - name: Push to Public Repo run: | echo "Setting up remote..." - git config --global user.name "prof-service-sync-bot" - git config --global user.email "prof-service-sync-bot@digits.schwarz" + git config --global user.name "ps-sync-bot" + git config --global user.email "ps-sync-bot@digits.schwarz" # Add the GitHub remote using the SSH protocol - git remote add public git@github.com:stackitcloud/professional-services.git + git remote add public git@github.com:stackitcloud/professional-service.git echo "Pushing main branch to GitHub..." git push public main --force diff --git a/.gitignore b/.gitignore index 26bffc0..50637e9 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,7 @@ go.work.sum ### Jetbrains .idea ssh +keys + +### K8s +.kubeconfig diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000..8b1c7fc --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,41 @@ +# Project Governance: STACKIT Professional Service + +This document defines the management, ownership, and maintenance processes for the STACKIT Professional Service repository. + +## 1. Strategy & "The Story" + +This repository serves as a bridge between internal excellence and public visibility. + +- **Internal Git (Source of Truth):** The primary repository is hosted on our internal STACKIT Git instance. All internal communication, documentation, and chat links MUST point to the internal instance to promote our own infrastructure and tools. +- **GitHub (Public Mirror):** The GitHub repository is a mirror intended for external visibility, SEO, and accessibility for AI models (LLMs). It helps customers find our solutions and establishes STACKIT as a thought leader in cloud automation. + +## 2. Ownership + +### 2.1 Organizational Ownership + +The repository is owned by the **STACKIT Professional Services** organization. High-level decisions regarding repository structure, licensing, and global policies are managed by the Core Maintainers team. + +### 2.2 Example & Module Ownership + +Individual examples or modules within the repository have specific owners, documented in their respective `MAINTAINERS.md` files. + +- **Responsibility:** Owners are responsible for the technical health, periodic updates (e.g., dependency bumps), and community feedback for their specific content. +- **Handover:** If an owner leaves the project or company, ownership reverts to the Core Maintainers until a new owner is assigned. + +## 3. Review & Quality Assurance + +To ensure high standards and security, we follow a strict contribution process: + +- **4-Eyes Principle:** No code enters the `main` branch without at least one successful Peer Review. +- **Automated Validation:** Every Pull Request must pass the CI pipeline, which includes: + - Linting and formatting checks. + - License header verification (Apache 2.0). + - Secret scanning (Trufflehog). +- **Best Effort Policy:** While we strive for quality, the content is provided "as-is." Use in production environments requires independent validation by the user. + +## 4. Mirroring Process + +The synchronization between the internal Git and GitHub is fully automated: + +1. Changes are merged into the internal `main` branch. +2. A GitHub Action triggers on every push to `main`. diff --git a/README.md b/README.md index 97b25cd..9fce77a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# STACKIT Professional Services +# STACKIT Professional Service -Welcome to the central repository for STACKIT Professional Services examples, scripts, and boilerplate code! +Welcome to the central repository for STACKIT examples, scripts, and boilerplate code! > **⚠️ REPOSITORY MIRROR NOTICE** > > This GitHub repository is a **mirror**. > The primary, internal source of truth for this codebase lives at: -> `https://professional-service.git.onstackit.cloud/professional-service-best-practices/professional-services` +> `https://professional-service.git.onstackit.cloud/professional-service-best-practices/professional-service` > > We automatically sync changes from our STACKIT managed GIT instance to this public GitHub repository. > @@ -20,7 +20,13 @@ Let's be upfront about how this repository is maintained: - **Strictly Best Effort:** Everything you find in this repository is provided on a "best effort" basis. - **No Guarantees on Freshness:** We try our best to keep the examples, Terraform modules, and scripts up to date with the latest provider releases and API changes. However, **we cannot guarantee it**. Things move fast in the cloud, and some examples might become outdated over time. -- **Use Your Brain:** Do not blindly copy-paste code from here directly into a production environment. +- **Review Before Deploying:** Do not blindly copy-paste code from here directly into a production environment. + +## Contents + +- [`examples/`](./examples) — Example solutions across a variety of STACKIT products. +- [`scripts/`](./scripts/README.md) — Helper scripts for working with STACKIT services. +- [`modules/`](./modules) — Ready-made Terraform modules to simplify your deployments. ## How to Use This Repository diff --git a/examples/dbaas-otel-collect-metrics/.terraform.lock.hcl b/examples/dbaas-otel-collect-metrics/.terraform.lock.hcl new file mode 100644 index 0000000..810732d --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/.terraform.lock.hcl @@ -0,0 +1,107 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/helm" { + version = "3.1.1" + hashes = [ + "h1:47CqNwkxctJtL/N/JuEj+8QMg8mRNI/NWeKO5/ydfZU=", + "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.1.0" + constraints = ">= 2.14.0" + hashes = [ + "h1:G9QqKNpcztBRqrywtlNylFJSpGzDfRFtO8hcWLdkvRY=", + "h1:oodIAuFMikXNmEtil5MQgP4dfSctUBYQiGJfjbsF3NY=", + "zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0", + "zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20", + "zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c", + "zh:1678c982853ad461e65ccb5e79d585e13ed109dd47dab2a66d3a7a304faeef65", + "zh:1c050a5c15e330457a9c18caacf61a923c59d663e13f2962e4b32f04fef523a0", + "zh:2c55bcec83be58ec132c7cb0a1ac644758b800d794fdc636d53a0eada0358a3a", + "zh:a062bb0aa316c08d8460c66a5d68da71da40de5d3bc3b31abcf3a1a9a19650f1", + "zh:a26fdea0afaa9b247c73c0b42843ca51ba7db0ac2571f9d3d50dcabd20ca1b98", + "zh:c872c9385a78d502bf5823d61cd3bb0f9a0585030e025eb12585c83451beeaa1", + "zh:f180879af931182beee4c8c0d9dab62b81d86f17ddcbe3786ef4c7cec9163a4e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f70f5789264069e0eef06f9b5d5fde955ef7206f7d446d1ce51a4c37a3f3e02f", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.8.1" + constraints = ">= 3.6.3" + hashes = [ + "h1:Eexl06+6J+s75uD46+WnZtpJZYRVUMB0AiuPBifK6Jc=", + "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", + "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", + "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", + "zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57", + "zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0", + "zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66", + "zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9", + "zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05", + "zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8", + "zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b", + "zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.13.1" + hashes = [ + "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", + "h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=", + "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", + "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", + "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", + "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", + "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", + "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", + "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", + "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", + "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", + "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.92.0" + constraints = ">= 0.87.0" + hashes = [ + "h1:dE5sdzUaHkzVL8AW3+GXD2EEWX2PlS+sHT7F25SXcZ0=", + "h1:j26ncxqlAp4q0/NHFoiATuVdIg7KH0zZhWoSAd+4Yj0=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:5eaa713f68a004ec33697f510ca4c7722940e2bab8080c025822ca547325ef98", + "zh:60ed4496492b9781f7cc581e346222a6356538a527e4ac67dce6815a64fc5c66", + "zh:6834a7819429e3482a5fdd547c442cc032d7047c3fb0dee30e8babb2438598e1", + "zh:6de632db0cbb42b429a9e752078df37716b0f335e5c39e883be5c55f7f1da553", + "zh:ac8b1bc8212236aaab789cef1dce718e6b8394bcf4b5f6c6f8dabf8c8a213573", + "zh:af4b1e805d6082a3ec94d2f5b68e8a62f04205af3f75a4a7d1b167e0f027d9ec", + "zh:b709258a4cd3acd0a9426809c1d7c1ed25859010b566c1b29481b132a7e2af13", + "zh:c7e8c5e8f2ca8c14c1bf5c92716a761b67792b38046b99653bdbf9ca423fc675", + "zh:c7f47c6b7e33d1f28bdc8d1aa5fda2734d74d6b1b0c6ef8b258489d9405af231", + "zh:d57dc6ad6b3a2879aa47012faf82f597a2ca1c3de1561bb96c6191e65072ea95", + "zh:d5b18390104164477913ced864e7a1cd5a678490f9412be887e5d8e3961d242e", + "zh:ead616306ab18c30a4c1110ad7fa8aee7d8a99e4410ceecbe5875beac5724f8a", + "zh:f73ad70183a35e5d04e4b48c44654c76fec48a8f4c913dd31a5befc2a1c2e4dc", + ] +} diff --git a/examples/dbaas-otel-collect-metrics/00-provider.tf b/examples/dbaas-otel-collect-metrics/00-provider.tf new file mode 100644 index 0000000..8bf8bc1 --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/00-provider.tf @@ -0,0 +1,55 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file defines the required Terraform providers and their configurations. +# It sets up the STACKIT, Kubernetes, and Helm providers to manage resources in the project and the SKE cluster. +terraform { + required_version = ">= 0.14.0" + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.87.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.6.3" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">=2.14.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + experiments = ["iam"] +} + +provider "kubernetes" { + host = yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.certificate-authority-data) +} + +provider "helm" { + kubernetes = { + host = yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.certificate-authority-data) + } +} diff --git a/examples/dbaas-otel-collect-metrics/01-variables.tf b/examples/dbaas-otel-collect-metrics/01-variables.tf new file mode 100644 index 0000000..35ec61c --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/01-variables.tf @@ -0,0 +1,33 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_project_id" { + type = string + default = "d75e6aab-b616-4b42-ae3b-aaf161ad626d" +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string + default = "../../keys/stackit-sa.json" +} + +resource "stackit_key_pair" "admin_keypair" { + name = "admin-keypair-12345" + public_key = chomp(file("~/.ssh/id_rsa.pub")) +} diff --git a/examples/dbaas-otel-collect-metrics/02-ske.tf b/examples/dbaas-otel-collect-metrics/02-ske.tf new file mode 100644 index 0000000..173ccbb --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/02-ske.tf @@ -0,0 +1,67 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_ske_kubeconfig" "this" { + project_id = var.stackit_project_id + cluster_name = stackit_ske_cluster.this.name + refresh = true + + depends_on = [stackit_ske_cluster.this] +} + +data "stackit_ske_kubernetes_versions" "this" { + version_state = "SUPPORTED" +} + +data "stackit_ske_machine_image_versions" "this" { + version_state = "SUPPORTED" +} + +locals { + flatcar_supported_version = one(flatten([ + for mi in data.stackit_ske_machine_image_versions.this.machine_images : [ + for v in mi.versions : + v.version + if mi.name == "flatcar" + ] + ])) +} + +resource "stackit_ske_cluster" "this" { + project_id = var.stackit_project_id + name = "dbaas-otel" + kubernetes_version_min = data.stackit_ske_kubernetes_versions.this.kubernetes_versions.0.version + + maintenance = { + enable_kubernetes_version_updates = true + enable_machine_image_version_updates = true + start = "01:00:00Z" + end = "02:00:00Z" + } + + node_pools = [ + { + name = "standard" + machine_type = "g2i.4" + minimum = "3" + maximum = "9" + max_surge = "3" + availability_zones = ["eu01-1", "eu01-2", "eu01-3"] + os_version_min = local.flatcar_supported_version + os_name = "flatcar" + volume_size = 150 + volume_type = "storage_premium_perf6" + }, + ] +} diff --git a/examples/dbaas-otel-collect-metrics/03-observability.tf b/examples/dbaas-otel-collect-metrics/03-observability.tf new file mode 100644 index 0000000..916d7db --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/03-observability.tf @@ -0,0 +1,20 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_observability_instance" "example" { + project_id = var.stackit_project_id + name = "example-obs" + plan_name = "Observability-Large-EU01" + alert_config = null +} diff --git a/examples/dbaas-otel-collect-metrics/04-postgres.tf b/examples/dbaas-otel-collect-metrics/04-postgres.tf new file mode 100644 index 0000000..6247083 --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/04-postgres.tf @@ -0,0 +1,44 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_postgresflex_instance" "this" { + project_id = var.stackit_project_id + name = "example-instance" + backup_schedule = "00 00 * * *" + flavor = { + cpu = 2 + ram = 4 + } + replicas = 3 + storage = { + class = "premium-perf2-stackit" + size = 15 + } + version = 15 + acl = ["0.0.0.0/0"] +} + +resource "stackit_postgresflex_user" "this" { + project_id = var.stackit_project_id + instance_id = stackit_postgresflex_instance.this.instance_id + username = "test" + roles = ["createdb", "login"] +} + +resource "stackit_postgresflex_database" "this" { + project_id = var.stackit_project_id + instance_id = stackit_postgresflex_instance.this.instance_id + name = "test" + owner = stackit_postgresflex_user.this.username +} diff --git a/examples/dbaas-otel-collect-metrics/04-service-account.tf b/examples/dbaas-otel-collect-metrics/04-service-account.tf new file mode 100644 index 0000000..eb25d4c --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/04-service-account.tf @@ -0,0 +1,38 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_service_account" "this" { + name = "prom-proxy" + project_id = var.stackit_project_id +} + +resource "time_rotating" "rotate" { + rotation_days = 150 +} + +resource "stackit_service_account_key" "this" { + project_id = var.stackit_project_id + service_account_email = stackit_service_account.this.email + ttl_days = 180 + + rotate_when_changed = { + rotation = time_rotating.rotate.id + } +} + +resource "stackit_authorization_project_role_assignment" "this" { + resource_id = var.stackit_project_id + role = "prometheus-proxy.reader" + subject = stackit_service_account.this.email +} diff --git a/examples/dbaas-otel-collect-metrics/05-otel-helm.tf b/examples/dbaas-otel-collect-metrics/05-otel-helm.tf new file mode 100644 index 0000000..acc8ee6 --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/05-otel-helm.tf @@ -0,0 +1,65 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +locals { + sa_json = jsondecode(stackit_service_account_key.this.json) + otel_helm_values = templatefile("${path.module}/helm-values/otel-collector-values.tftpl", { + stackit_project_id = var.stackit_project_id + stackit_region = var.stackit_region + stackit_postgres_instance_id = stackit_postgresflex_instance.this.instance_id + observability_metrics_endpoint = stackit_observability_instance.example.metrics_push_url + secret_name = kubernetes_secret.otel_secret.metadata[0].name + sa_client_id = local.sa_json.credentials.sub + sa_issuer = local.sa_json.credentials.iss + sa_key_id = local.sa_json.credentials.kid + }) +} + + +resource "stackit_observability_credential" "otel" { + project_id = var.stackit_project_id + instance_id = stackit_observability_instance.example.instance_id +} + +resource "kubernetes_namespace" "monitoring" { + metadata { + name = "monitoring" + } +} + +resource "kubernetes_secret" "otel_secret" { + metadata { + name = "otel-secrets" + namespace = kubernetes_namespace.monitoring.metadata[0].name + } + + data = { + OBSERVABILITY_AUTHORIZATION_HEADER = "Basic ${base64encode("${stackit_observability_credential.otel.username}:${stackit_observability_credential.otel.password}")}" + JSON = stackit_service_account_key.this.json + PRIVATE_KEY = jsondecode(stackit_service_account_key.this.json).credentials.privateKey + } +} + +resource "helm_release" "opentelemetry_collector" { + name = "opentelemetry-collector" + repository = "https://open-telemetry.github.io/opentelemetry-helm-charts" + chart = "opentelemetry-collector" + version = "0.152.0" + namespace = kubernetes_namespace.monitoring.metadata[0].name + timeout = 30 + + values = [ + local.otel_helm_values + ] +} diff --git a/examples/dbaas-otel-collect-metrics/MAINTAINERS.md b/examples/dbaas-otel-collect-metrics/MAINTAINERS.md new file mode 100644 index 0000000..6457701 --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (Mauritz.Uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/dbaas-otel-collect-metrics/README.md b/examples/dbaas-otel-collect-metrics/README.md new file mode 100644 index 0000000..965b11b --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/README.md @@ -0,0 +1,39 @@ +# DBaaS OpenTelemetry Metrics Collection + +Collect metrics from STACKIT PostgreSQL Flex and MongoDB instances using OpenTelemetry (OTel) and export them to STACKIT Observability. + +## Prerequisites + +- STACKIT Project ID and Service Account key. +- Terraform, `kubectl`, and `helm` installed. + +## Usage + +1. **Configure**: Update `stackit_project_id` and `stackit_service_account_key_path` in `01-variables.tf`. +2. **Deploy**: + ```bash + terraform init + terraform apply + ``` + +## Scrape Configuration + +The OTel Collector scrapes metrics from: + +- **PostgreSQL**: `https://postgres-prom-proxy.api.stackit.cloud/v2/...` +- **MongoDB**: `https://mongodb-prom-proxy.api.stackit.cloud/v2/...` + +_Note: MSSQL is not supported._ + +## Debugging + +View live scrape data in the collector logs: + +```bash +kubectl logs -l app.kubernetes.io/name=otel-collector -n monitoring -f +``` + +## Documentation + +- [PostgreSQL Flex Metrics](https://docs.stackit.cloud/products/databases/postgresql-flex/reference/observability-metrics-in-postgresql-flex/) +- [MongoDB Flex Metrics](https://docs.stackit.cloud/products/databases/mongodb-flex/reference/observability-metrics/) diff --git a/examples/dbaas-otel-collect-metrics/helm-values/otel-collector-values.tftpl b/examples/dbaas-otel-collect-metrics/helm-values/otel-collector-values.tftpl new file mode 100644 index 0000000..2f34dd0 --- /dev/null +++ b/examples/dbaas-otel-collect-metrics/helm-values/otel-collector-values.tftpl @@ -0,0 +1,79 @@ +fullnameOverride: otel-collector +mode: deployment + +podAnnotations: + stackit-sa-key-id: "${sa_key_id}" + +image: + repository: "otel/opentelemetry-collector-contrib" + +config: + receivers: + prometheus: + config: + scrape_configs: + - job_name: stackit-postgres + metrics_path: /v2/projects/$${STACKIT_PROJECT_ID}/regions/$${STACKIT_REGION}/instances/$${STACKIT_POSTGRES_INSTANCE_ID}/metrics + oauth2: + audience: $${SA_TOKEN_REQUEST_AUDIENCE} + client_certificate_key_file: /mnt/secrets-store/private-key + client_certificate_key_id: $${SA_TOKEN_REQUEST_CLIENT_CERTIFICATE_KEY_ID} + client_id: $${SA_TOKEN_REQUEST_CLIENT_ID} + grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer + iss: $${SA_TOKEN_REQUEST_ISSUER} + signature_algorithm: RS512 + token_url: https://service-account.api.stackit.cloud/token + scheme: https + scrape_interval: 1m + static_configs: + - targets: + - postgres-prom-proxy.api.stackit.cloud:443 + exporters: + debug: + verbosity: normal + prometheusremotewrite: + endpoint: $${OBSERVABILITY_METRICS_ENDPOINT} + headers: + Authorization: $${OBSERVABILITY_AUTHORIZATION_HEADER} + + service: + pipelines: + metrics: + receivers: [prometheus] + exporters: [prometheusremotewrite, debug] + +extraEnvs: + - name: STACKIT_PROJECT_ID + value: "${stackit_project_id}" + - name: STACKIT_REGION + value: "${stackit_region}" + - name: STACKIT_POSTGRES_INSTANCE_ID + value: "${stackit_postgres_instance_id}" + - name: OBSERVABILITY_METRICS_ENDPOINT + value: "${observability_metrics_endpoint}" + - name: OBSERVABILITY_AUTHORIZATION_HEADER + valueFrom: + secretKeyRef: + name: ${secret_name} + key: OBSERVABILITY_AUTHORIZATION_HEADER + - name: SA_TOKEN_REQUEST_CLIENT_ID + value: "${sa_client_id}" + - name: SA_TOKEN_REQUEST_ISSUER + value: "${sa_issuer}" + - name: SA_TOKEN_REQUEST_CLIENT_CERTIFICATE_KEY_ID + value: "${sa_key_id}" + - name: SA_TOKEN_REQUEST_AUDIENCE + value: "https://service-account.api.stackit.cloud/token" + +extraVolumes: + - name: otel-secrets + secret: + secretName: ${secret_name} + items: + - key: PRIVATE_KEY + path: private-key + +extraVolumeMounts: + - name: otel-secrets + mountPath: /mnt/secrets-store + readOnly: true diff --git a/examples/iaas-cross-az-layer4-loadbalancer/.terraform.lock.hcl b/examples/iaas-cross-az-layer4-loadbalancer/.terraform.lock.hcl new file mode 100644 index 0000000..eeb1632 --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/.terraform.lock.hcl @@ -0,0 +1,46 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/random" { + version = "3.8.1" + constraints = ">= 3.6.3" + hashes = [ + "h1:Eexl06+6J+s75uD46+WnZtpJZYRVUMB0AiuPBifK6Jc=", + "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", + "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", + "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", + "zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57", + "zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0", + "zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66", + "zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9", + "zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05", + "zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8", + "zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b", + "zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.91.0" + constraints = ">= 0.87.0" + hashes = [ + "h1:8de9n+Roq6Z2Ltp9poBBBN9a4zSpx73VLpgFS5mTyoI=", + "h1:RStdHSDwbtonYfg7mR5Y92v6fxIVX9FEz0UN+tm9kHI=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:0ed12db90276ccd2d6f87135b7dd078657823c3ca33121c6a157d0bdf08f801e", + "zh:160b32bcf1d01666784cf8469e10e0a38d4c3d24c80c0c5be470cc63ef27ea62", + "zh:32e1909037235c24138b74131c6fb12ac99003f79750f1768ca5468cc05da6b0", + "zh:4376f1cdafbb35ad5f220e28153741908390b23161d9eae3828f7830039ce8ef", + "zh:458b054781ef6165d9136fc3d667f9bf37319e37d0f19300bbb63b703de2599d", + "zh:54a1864cf1315a118c043f834e02f2a1ca0ecbc8c2a246460589a95847da6c80", + "zh:83424712926ccef3c60cc011dfa298721bdbaee3598a0c8459da46bc6b7424cc", + "zh:a3c38ebffdbca21dd177b06acf891bed1a903907ba252d0219d91ff0ecf9d861", + "zh:c6325e583b77aa1e9df94e3b4b12479d7bf12c66a2ace71c1b8f64e46ac5c37e", + "zh:de6db8deeee895af5670df2449c8b8c34df051277f8a6e2f19c5c9ec1f0ddb12", + "zh:e18b05e7d8356caa6103c5c80b5ea373be3ff255b453cf577c68798ffe1b93ce", + "zh:f4d9215f7a2888c882892642539b2edd3ea97cb25904e4fa358db4f001c3ccd0", + "zh:f94d0c0c2bf843867122ababc8d8066d52257e68bbcb5c62a603f77c581e9668", + ] +} diff --git a/examples/iaas-cross-az-layer4-loadbalancer/00-provider.tf b/examples/iaas-cross-az-layer4-loadbalancer/00-provider.tf new file mode 100644 index 0000000..da9ff5a --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/00-provider.tf @@ -0,0 +1,33 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Define required providers +terraform { + required_version = ">= 0.14.0" + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.87.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.6.3" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path +} diff --git a/examples/iaas-cross-az-layer4-loadbalancer/01-variables.tf b/examples/iaas-cross-az-layer4-loadbalancer/01-variables.tf new file mode 100644 index 0000000..efb773e --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/01-variables.tf @@ -0,0 +1,37 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_project_id" { + type = string + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string + default = "../../keys/stackit-sa.json" +} + +resource "stackit_key_pair" "admin_keypair" { + name = "admin-keypair-12345" + public_key = chomp(file("~/.ssh/id_rsa.pub")) +} + +variable "jumphost_flavor" { + default = "c2i.1" +} diff --git a/examples/iaas-cross-az-layer4-loadbalancer/02-network.tf b/examples/iaas-cross-az-layer4-loadbalancer/02-network.tf new file mode 100644 index 0000000..29222d3 --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/02-network.tf @@ -0,0 +1,20 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_network" "network" { + project_id = var.stackit_project_id + name = "network01" + ipv4_nameservers = ["1.1.1.1", "9.9.9.9"] + ipv4_prefix = "172.17.1.0/24" +} diff --git a/examples/iaas-cross-az-layer4-loadbalancer/03-machine01.tf b/examples/iaas-cross-az-layer4-loadbalancer/03-machine01.tf new file mode 100644 index 0000000..cd16c5a --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/03-machine01.tf @@ -0,0 +1,27 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "test-machine01" { + source = "../../modules/test-machine" + + project_id = var.stackit_project_id + network_id = stackit_network.network.network_id + availability_zone = "eu01-1" + + name = "machine01" + machine_type = var.jumphost_flavor + disk_size = 48 + + user_data = templatefile("${path.module}/apache-debug-user.yaml", {}) +} diff --git a/examples/iaas-cross-az-layer4-loadbalancer/04-machine02.tf b/examples/iaas-cross-az-layer4-loadbalancer/04-machine02.tf new file mode 100644 index 0000000..956ccd2 --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/04-machine02.tf @@ -0,0 +1,27 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "test-machine02" { + source = "../../modules/test-machine" + + project_id = var.stackit_project_id + network_id = stackit_network.network.network_id + availability_zone = "eu01-2" + + name = "machine02" + machine_type = var.jumphost_flavor + disk_size = 48 + + user_data = templatefile("${path.module}/apache-debug-user.yaml", {}) +} diff --git a/examples/iaas-cross-az-layer4-loadbalancer/05-l4-loadbalancer.tf b/examples/iaas-cross-az-layer4-loadbalancer/05-l4-loadbalancer.tf new file mode 100644 index 0000000..9d86f6c --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/05-l4-loadbalancer.tf @@ -0,0 +1,84 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_public_ip" "public_ip" { + project_id = var.stackit_project_id + + lifecycle { + ignore_changes = [network_interface_id] + } +} + +resource "stackit_loadbalancer" "this" { + project_id = var.stackit_project_id + name = "lb-example-1" + disable_security_group_assignment = true + + target_pools = [ + { + name = "pool-1" + target_port = 80 + targets = [ + { + display_name = "lb-target-1" + ip = module.test-machine01.primary_ip + }, + { + display_name = "lb-target-2" + ip = module.test-machine02.primary_ip + } + ] + active_health_check = { + healthy_threshold = 10 + interval = "3s" + interval_jitter = "3s" + timeout = "3s" + unhealthy_threshold = 10 + } + }, + ] + + listeners = [ + { + display_name = "listener1" + port = 80 + protocol = "PROTOCOL_TCP" + target_pool = "pool-1" + }, + ] + + networks = [ + { + network_id = stackit_network.network.network_id + role = "ROLE_LISTENERS_AND_TARGETS" + } + ] + + external_address = stackit_public_ip.public_ip.ip + + options = { + // for private loadbalancer usage + /*private_network_only = false*/ + } +} + + +output "lb_external_address" { + value = stackit_loadbalancer.this.external_address +} + +/*output "lb_private_ip_address" { + // for private loadbalancer usage + value = stackit_loadbalancer.lb_example.private_address +}*/ diff --git a/examples/iaas-cross-az-layer4-loadbalancer/MAINTAINERS.md b/examples/iaas-cross-az-layer4-loadbalancer/MAINTAINERS.md new file mode 100644 index 0000000..6457701 --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (Mauritz.Uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/iaas-cross-az-layer4-loadbalancer/README.md b/examples/iaas-cross-az-layer4-loadbalancer/README.md new file mode 100644 index 0000000..e07830c --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/README.md @@ -0,0 +1,5 @@ +# IaaS cross AZ Layer4 Loadbalancer + +## Overview + +A classic highly-available architecture: provisioning multiple VMs across different Availability Zones (AZs) and putting them behind a STACKIT L4 Load Balancer. diff --git a/examples/iaas-cross-az-layer4-loadbalancer/apache-debug-user.yaml b/examples/iaas-cross-az-layer4-loadbalancer/apache-debug-user.yaml new file mode 100644 index 0000000..c44cda9 --- /dev/null +++ b/examples/iaas-cross-az-layer4-loadbalancer/apache-debug-user.yaml @@ -0,0 +1,22 @@ +#cloud-config +users: + - name: debug + groups: sudo + shell: /bin/bash + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + lock_passwd: false + passwd: "$6$JZBVJ2zsw/o4C1UJ$FskGQWf.nqwj.o9bHbxkSGvSilQcHt03KdPYlgsiE3L77tNqFj0/vnlCXSf.SRb4jR2xsHk/.OlEyT16Txj4J." # hashed version of 'House123!' + +chpasswd: + expire: false + +ssh_pwauth: true + +packages: + - apache2 + +runcmd: + - systemctl enable apache2 + - systemctl start apache2 + - echo "

Hello from STACKIT Instance

Hostname $(hostname)

" > /var/www/html/index.html + - chown www-data:www-data /var/www/html/index.html diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/.terraform-version b/examples/iaas-cross-az-layer7-loadbalancer-waf/.terraform-version new file mode 100644 index 0000000..79f9beb --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/.terraform-version @@ -0,0 +1 @@ +v1.14.0 diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/.terraform.lock.hcl b/examples/iaas-cross-az-layer7-loadbalancer-waf/.terraform.lock.hcl new file mode 100644 index 0000000..25ece83 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/.terraform.lock.hcl @@ -0,0 +1,90 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/random" { + version = "3.8.1" + constraints = ">= 3.6.3" + hashes = [ + "h1:Eexl06+6J+s75uD46+WnZtpJZYRVUMB0AiuPBifK6Jc=", + "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", + "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", + "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", + "zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57", + "zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0", + "zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66", + "zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9", + "zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05", + "zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8", + "zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b", + "zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.2.1" + hashes = [ + "h1:F5d6bQY8UlBo0D71Sv7CsV+3aZOFz0yeNF+vufog7h4=", + "h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=", + "zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a", + "zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86", + "zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad", + "zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191", + "zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b", + "zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5", + "zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635", + "zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933", + "zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e", + "zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed", + "zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/mastercard/restapi" { + version = "3.0.0" + constraints = ">= 3.0.0" + hashes = [ + "h1:Fqxoc6bsydl6iWGx6ZvyqUDdGt7Cb4sW/BSHhBeHGgw=", + "h1:y1I3azDHOqRySTyDHsb3Xh1waP/99KfykZRagbRx1qI=", + "zh:0b63bd3c25a31f090a41933f90b7dd6e984add1c4261d8f5caa73f4d5aa065a4", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:2d31f322454d271eb328c2d3b3d41f426df98503982788be347799ddf68bf9bf", + "zh:47dd97e3f43bb89ae4254bba90ffbc6d521338554a1f94961e21214dd801b81b", + "zh:49636b072b9a30d15916468857bce91d39bc87bbba1c99fb3894fafa9409b8b4", + "zh:5566605a8e16478bc66c1fec8dea0890586c084221161dc82b73d162d44c08a7", + "zh:5859e0ad05aa6b3b108f0b718986e237a18d5176efea62d1ac1ef352561b4713", + "zh:76129b89e2b56d8d2af8f6e10cc748bea4ee6ec1105e916f1254cd124f4dcf9c", + "zh:bfc20b5fd03cb3243917e8cf360e5208284e757ab82f83c992da471ef16a0eab", + "zh:d1d2363009253cdfe5795a48b6412bff11104fe6a52fb0a57e5a95fc765a161e", + "zh:d1f0b981089ad709b73c4f989a9cd9118c4e3cb8fc0a2b303aa4d77cc5102a53", + "zh:dbfddb2f407481a4e88fdc17739c805d9d9fff2451efcb9226572d59ed2e9128", + "zh:df04a8c777d05896684171807b27c41befbf5f217f50b0e9b2b27164d4aacca5", + "zh:e68b450c66efe55d1132585477fa71207680806edafb3792ca44d9695d0a1d75", + "zh:f894e7e9913347e25e67d5d3bf91659c06877dd5fa11acf75820fa03fa34b8bd", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.91.0" + constraints = ">= 0.87.0" + hashes = [ + "h1:8de9n+Roq6Z2Ltp9poBBBN9a4zSpx73VLpgFS5mTyoI=", + "h1:RStdHSDwbtonYfg7mR5Y92v6fxIVX9FEz0UN+tm9kHI=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:0ed12db90276ccd2d6f87135b7dd078657823c3ca33121c6a157d0bdf08f801e", + "zh:160b32bcf1d01666784cf8469e10e0a38d4c3d24c80c0c5be470cc63ef27ea62", + "zh:32e1909037235c24138b74131c6fb12ac99003f79750f1768ca5468cc05da6b0", + "zh:4376f1cdafbb35ad5f220e28153741908390b23161d9eae3828f7830039ce8ef", + "zh:458b054781ef6165d9136fc3d667f9bf37319e37d0f19300bbb63b703de2599d", + "zh:54a1864cf1315a118c043f834e02f2a1ca0ecbc8c2a246460589a95847da6c80", + "zh:83424712926ccef3c60cc011dfa298721bdbaee3598a0c8459da46bc6b7424cc", + "zh:a3c38ebffdbca21dd177b06acf891bed1a903907ba252d0219d91ff0ecf9d861", + "zh:c6325e583b77aa1e9df94e3b4b12479d7bf12c66a2ace71c1b8f64e46ac5c37e", + "zh:de6db8deeee895af5670df2449c8b8c34df051277f8a6e2f19c5c9ec1f0ddb12", + "zh:e18b05e7d8356caa6103c5c80b5ea373be3ff255b453cf577c68798ffe1b93ce", + "zh:f4d9215f7a2888c882892642539b2edd3ea97cb25904e4fa358db4f001c3ccd0", + "zh:f94d0c0c2bf843867122ababc8d8066d52257e68bbcb5c62a603f77c581e9668", + ] +} diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/00-provider.tf b/examples/iaas-cross-az-layer7-loadbalancer-waf/00-provider.tf new file mode 100644 index 0000000..8673fee --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/00-provider.tf @@ -0,0 +1,48 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Define required providers +terraform { + required_version = ">= 0.14.0" + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.87.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.6.3" + } + restapi = { + source = "Mastercard/restapi" + version = ">= 3.0.0" + } + } +} + +ephemeral "stackit_access_token" "alb" {} + +provider "restapi" { + uri = "https://alb-waf.api.stackit.cloud" + bearer_token = ephemeral.stackit_access_token.alb.access_token + + id_attribute = "name" + write_returns_object = true +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + enable_beta_resources = true +} diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/01-variables.tf b/examples/iaas-cross-az-layer7-loadbalancer-waf/01-variables.tf new file mode 100644 index 0000000..efb773e --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/01-variables.tf @@ -0,0 +1,37 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_project_id" { + type = string + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string + default = "../../keys/stackit-sa.json" +} + +resource "stackit_key_pair" "admin_keypair" { + name = "admin-keypair-12345" + public_key = chomp(file("~/.ssh/id_rsa.pub")) +} + +variable "jumphost_flavor" { + default = "c2i.1" +} diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/02-network.tf b/examples/iaas-cross-az-layer7-loadbalancer-waf/02-network.tf new file mode 100644 index 0000000..29222d3 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/02-network.tf @@ -0,0 +1,20 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_network" "network" { + project_id = var.stackit_project_id + name = "network01" + ipv4_nameservers = ["1.1.1.1", "9.9.9.9"] + ipv4_prefix = "172.17.1.0/24" +} diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/03-machine01.tf b/examples/iaas-cross-az-layer7-loadbalancer-waf/03-machine01.tf new file mode 100644 index 0000000..1972d38 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/03-machine01.tf @@ -0,0 +1,28 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "test-machine01" { + source = "../../modules/test-machine" + + project_id = var.stackit_project_id + network_id = stackit_network.network.network_id + availability_zone = "eu01-1" + security_enabled = true + + name = "machine01" + machine_type = var.jumphost_flavor + disk_size = 48 + + user_data = templatefile("${path.module}/apache-debug-user.yaml", {}) +} diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/04-machine02.tf b/examples/iaas-cross-az-layer7-loadbalancer-waf/04-machine02.tf new file mode 100644 index 0000000..a197447 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/04-machine02.tf @@ -0,0 +1,28 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "test-machine02" { + source = "../../modules/test-machine" + + project_id = var.stackit_project_id + network_id = stackit_network.network.network_id + availability_zone = "eu01-2" + security_enabled = true + + name = "machine02" + machine_type = var.jumphost_flavor + disk_size = 48 + + user_data = templatefile("${path.module}/apache-debug-user.yaml", {}) +} diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/05-l7-loadbalancer.tf b/examples/iaas-cross-az-layer7-loadbalancer-waf/05-l7-loadbalancer.tf new file mode 100644 index 0000000..969dfc7 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/05-l7-loadbalancer.tf @@ -0,0 +1,117 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "tls_private_key" "example" { + algorithm = "RSA" + rsa_bits = 2048 +} + +resource "tls_self_signed_cert" "example" { + private_key_pem = tls_private_key.example.private_key_pem + + subject { + common_name = "localhost" + organization = "STACKIT Test" + } + + validity_period_hours = 12 + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] +} + +resource "stackit_public_ip" "public_ip" { + project_id = var.stackit_project_id + + lifecycle { + ignore_changes = [network_interface_id] + } +} + +resource "stackit_alb_certificate" "this" { + project_id = var.stackit_project_id + name = "example-certificate" + private_key = tls_private_key.example.private_key_pem + public_key = tls_self_signed_cert.example.cert_pem +} + +resource "stackit_application_load_balancer" "this" { + project_id = var.stackit_project_id + region = var.stackit_region + name = "example-load-balancer" + plan_id = "p10" + external_address = stackit_public_ip.public_ip.ip + + listeners = [ + { + name = "listener01" + port = 443 + http = { + hosts = [{ + host = "*" + rules = [{ + target_pool = "target-pool-01" + /*path = { + prefix = "/path" + }*/ + }] + }] + } + https = { + certificate_config = { + certificate_ids = [ + stackit_alb_certificate.this.cert_id + ] + } + } + waf_config_name = restapi_object.waf.api_data.name + protocol = "PROTOCOL_HTTPS" + } + ] + networks = [ + { + network_id = stackit_network.network.network_id + role = "ROLE_LISTENERS_AND_TARGETS" + } + ] + target_pools = [ + { + name = "target-pool-01" + target_port = 80 + targets = [ + { + display_name = "server01" + ip = module.test-machine01.primary_ip + }, + { + display_name = "server02" + ip = module.test-machine02.primary_ip + } + ] + } + ] +} + + +output "alb_external_address" { + value = stackit_application_load_balancer.this.external_address +} + +/*output "alb_private_ip_address" { + // for private alb loadbalancer usage + value = stackit_application_load_balancer.this.private_address +}*/ diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/06-waf.tf b/examples/iaas-cross-az-layer7-loadbalancer-waf/06-waf.tf new file mode 100644 index 0000000..405898e --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/06-waf.tf @@ -0,0 +1,46 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "restapi_object" "waf_crs" { + path = "/v1alpha/projects/${var.stackit_project_id}/regions/${var.stackit_region}/core-rule-sets" + data = jsonencode({ + name = "example-crs" + active = true + }) + + ignore_server_additions = true +} + +resource "restapi_object" "waf_rules" { + path = "/v1alpha/projects/${var.stackit_project_id}/regions/${var.stackit_region}/rules" + data = jsonencode({ + name = "example-rules" + rules = file("${path.module}/example-waf.conf") + }) + + ignore_server_additions = true + depends_on = [restapi_object.waf_crs] +} + +resource "restapi_object" "waf" { + path = "/v1alpha/projects/${var.stackit_project_id}/regions/${var.stackit_region}/wafs" + data = jsonencode({ + name = "example-waf" + coreRuleSetName = restapi_object.waf_crs.api_data.name + rulesConfigName = restapi_object.waf_rules.api_data.name + }) + + ignore_server_additions = true + depends_on = [restapi_object.waf_rules] +} diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/MAINTAINERS.md b/examples/iaas-cross-az-layer7-loadbalancer-waf/MAINTAINERS.md new file mode 100644 index 0000000..6457701 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (Mauritz.Uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/README.md b/examples/iaas-cross-az-layer7-loadbalancer-waf/README.md new file mode 100644 index 0000000..895244b --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/README.md @@ -0,0 +1,36 @@ +# IaaS cross AZ Layer 7 Loadbalancer + +## Overview + +A classic highly-available architecture: provisioning multiple VMs across different Availability Zones (AZs) and putting them behind a STACKIT L7 Load Balancer. This example also includes a Web Application Firewall (WAF) configuration to secure the backend workloads against malicious traffic. + +## ⚠️ Important Note: [WAF Implementation](06-waf.tf) + +Currently, the official STACKIT Terraform provider does not natively support Web Application Firewall (WAF) resources. + +To bridge this gap and fully automate the deployment, this example utilizes a `restapi` provider as a workaround. This allows Terraform to interact directly with the STACKIT WAF REST API (`/v1alpha/projects/...`) to create and attach the Core Rule Sets and custom SecLang rules until native support is released. + +## Testing the WAF + +This deployment includes rules written in SecLang. These rules are specifically designed to safely verify that the WAF is successfully deployed, actively intercepting traffic, and applying your configurations. + +Once `terraform apply` completes successfully, extract the public IP of your Load Balancer from the Terraform outputs: + +```bash +# Export the Load Balancer IP to an environment variable +export ALB_IP=$(terraform output -raw alb_external_address) +``` + +Now, use curl to trigger the custom rules. Because the WAF is configured to block these specific signatures, both of the following commands should return an HTTP 403 Forbidden status code. + +Test 1: Trigger via Query Parameter + +```Bash +curl -k -I -X GET "https://${ALB_IP}/?waf_test=trigger" +``` + +Test 2: Trigger via Custom HTTP Header + +```Bash +curl -k -I -H "X-WAF-Test: trigger" "https://${ALB_IP}/" +``` diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/apache-debug-user.yaml b/examples/iaas-cross-az-layer7-loadbalancer-waf/apache-debug-user.yaml new file mode 100644 index 0000000..c44cda9 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/apache-debug-user.yaml @@ -0,0 +1,22 @@ +#cloud-config +users: + - name: debug + groups: sudo + shell: /bin/bash + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + lock_passwd: false + passwd: "$6$JZBVJ2zsw/o4C1UJ$FskGQWf.nqwj.o9bHbxkSGvSilQcHt03KdPYlgsiE3L77tNqFj0/vnlCXSf.SRb4jR2xsHk/.OlEyT16Txj4J." # hashed version of 'House123!' + +chpasswd: + expire: false + +ssh_pwauth: true + +packages: + - apache2 + +runcmd: + - systemctl enable apache2 + - systemctl start apache2 + - echo "

Hello from STACKIT Instance

Hostname $(hostname)

" > /var/www/html/index.html + - chown www-data:www-data /var/www/html/index.html diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/example-waf.conf b/examples/iaas-cross-az-layer7-loadbalancer-waf/example-waf.conf new file mode 100644 index 0000000..31067e4 --- /dev/null +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/example-waf.conf @@ -0,0 +1,23 @@ +# ------------------------------------------------------------------------ +# WAF TEST RULES +# Custom rule IDs should generally start at 1000000 to avoid conflicting +# with the OWASP Core Rule Set (which uses the 900000 - 999999 range). +# ------------------------------------------------------------------------ + +# Test Rule 1: Block based on a specific query parameter (?waf_test=trigger) +SecRule ARGS:waf_test "@streq trigger" \ + "id:1000001,\ + phase:1,\ + deny,\ + status:403,\ + log,\ + msg:'WAF Test Rule Triggered via Query Parameter'" + +# Test Rule 2: Block based on a specific custom header (X-WAF-Test: trigger) +SecRule REQUEST_HEADERS:X-WAF-Test "@streq trigger" \ + "id:1000002,\ + phase:1,\ + deny,\ + status:403,\ + log,\ + msg:'WAF Test Rule Triggered via Custom Header'" diff --git a/examples/iaas-ha-vrrp/.terraform.lock.hcl b/examples/iaas-ha-vrrp/.terraform.lock.hcl index 22969f3..c8e7f0a 100644 --- a/examples/iaas-ha-vrrp/.terraform.lock.hcl +++ b/examples/iaas-ha-vrrp/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/random" { version = "3.8.1" constraints = ">= 3.6.3" hashes = [ + "h1:Eexl06+6J+s75uD46+WnZtpJZYRVUMB0AiuPBifK6Jc=", "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", @@ -25,6 +26,7 @@ provider "registry.terraform.io/stackitcloud/stackit" { version = "0.90.0" constraints = ">= 0.87.0" hashes = [ + "h1:QgP6TOtucJ3A6fA51rdUvxhYGjl9RrWvXQZpjHTOuiU=", "h1:W29Kv6XUxYssF2Gy8KcmTx3EFstt6k8sKgPRIBbq+qs=", "zh:003af58a84884558bbb2fc40fcbefa6774ec20aa9e4b97cf3f950190a600afd2", "zh:026ee9cef4670cf33369f8654c6b9b1d8c0e116ceb0b353c882be222951ecdd4", diff --git a/examples/iaas-ha-vrrp/01-config.tf b/examples/iaas-ha-vrrp/01-config.tf index a0793e7..e01eeda 100644 --- a/examples/iaas-ha-vrrp/01-config.tf +++ b/examples/iaas-ha-vrrp/01-config.tf @@ -14,7 +14,7 @@ variable "stackit_project_id" { type = string - default = "d75e6aab-b616-4b42-ae3b-aaf161ad626d" + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } variable "stackit_region" { diff --git a/examples/iaas-volume-encryption/.terraform.lock.hcl b/examples/iaas-volume-encryption/.terraform.lock.hcl index 7c3993a..f4f00c9 100644 --- a/examples/iaas-volume-encryption/.terraform.lock.hcl +++ b/examples/iaas-volume-encryption/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/stackitcloud/stackit" { version = "0.80.0" constraints = "0.80.0" hashes = [ + "h1:VqmLlSV9sMOX7aq5Bnsj18KNKCUPFahZzf0SA5fTkVk=", "h1:wz7uGwzVoo1NO18CDLcfjLraTSiWQ5EzJnDeCKcFi60=", "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", "zh:3a0e6cb125ef76a24b2b5ff9c786c57058f385571d283bd68f633225fcca695a", diff --git a/examples/iaas-volume-encryption/01-config.tf b/examples/iaas-volume-encryption/01-config.tf index ba124eb..7fbb8c0 100644 --- a/examples/iaas-volume-encryption/01-config.tf +++ b/examples/iaas-volume-encryption/01-config.tf @@ -29,5 +29,5 @@ variable "zone" { variable "STACKIT_PROJECT_ID" { type = string description = "STACKIT Project ID" - default = "16ec118f-90d0-466d-8393-99eea504c536" + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } diff --git a/examples/iaas-volume-encryption/05-server.tf b/examples/iaas-volume-encryption/05-server.tf index 9cbc0d6..3c342fb 100644 --- a/examples/iaas-volume-encryption/05-server.tf +++ b/examples/iaas-volume-encryption/05-server.tf @@ -33,7 +33,7 @@ resource "stackit_network_interface" "nic" { data "stackit_security_group" "default" { project_id = var.STACKIT_PROJECT_ID - security_group_id = "a6b4708e-b8ee-48ba-b084-a4892e9a73af" + security_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } data "stackit_network" "default" { diff --git a/examples/iam-scim-integration/.terraform.lock.hcl b/examples/iam-scim-integration/.terraform.lock.hcl new file mode 100644 index 0000000..3a43f35 --- /dev/null +++ b/examples/iam-scim-integration/.terraform.lock.hcl @@ -0,0 +1,146 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/goauthentik/authentik" { + version = "2026.2.0" + constraints = "2026.2.0" + hashes = [ + "h1:On3/Zzv3W72aGsJ4AhW/tnpi4hvq9cxwgf7tF6Tg+a4=", + "zh:00c44e8ee842e75de9cc4fd6193b10258d1dc840e5be4aaaf118ffc180dceee0", + "zh:13057f08bce3b63613e1be3997dd454ff9568c569dd983987b1550280fbe3d01", + "zh:410a1ff2ae4647cc0ab37894f81e4d474b588a0a7f005d05d55e8c3a40978dd2", + "zh:43830834d12b3c0eeabe397842f82ca3a6b58a5bc8dd837d55b821419b55ed61", + "zh:56eaedd196ed7c4003cee0434b891b38242b4fde2031978d0ddcfdf6e16ee5ad", + "zh:5b3c10bb63c3c215ed9e0918e5808b240e3f2ee8248d10cd4d824a4998a213c5", + "zh:99c14891bcb92a6b21ef4c0e60f6c0df23e3452808f3eefd67cde78d132c80d9", + "zh:9a32cdda9f939f8484e27d4200d004c44f016fe97579a111201083f4beea78e8", + "zh:ae5086816144f68de9a0002e7696321169a71473f9d161793f4ae996388f56de", + "zh:bd09409dd34608a4ef3ea80cfc5e397268e7872f2e84c1ccdc9b5698e36ddad5", + "zh:be7af8b9eb61b0eb5053f14360e5a68caeb32c115efe8e1b583f2e7c91352a2a", + "zh:e11726812a1b2caf6b6784a3d074d1f50e3d406e9629c02096a001e5a5979331", + "zh:e39183d10d8158ccab51208f4f727c7419b1b1e596f4feb23dc42aebb36d01e3", + ] +} + +provider "registry.terraform.io/hashicorp/helm" { + version = "3.1.1" + hashes = [ + "h1:47CqNwkxctJtL/N/JuEj+8QMg8mRNI/NWeKO5/ydfZU=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.1.0" + constraints = "> 2.14.0" + hashes = [ + "h1:G9QqKNpcztBRqrywtlNylFJSpGzDfRFtO8hcWLdkvRY=", + "zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0", + "zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20", + "zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c", + "zh:1678c982853ad461e65ccb5e79d585e13ed109dd47dab2a66d3a7a304faeef65", + "zh:1c050a5c15e330457a9c18caacf61a923c59d663e13f2962e4b32f04fef523a0", + "zh:2c55bcec83be58ec132c7cb0a1ac644758b800d794fdc636d53a0eada0358a3a", + "zh:a062bb0aa316c08d8460c66a5d68da71da40de5d3bc3b31abcf3a1a9a19650f1", + "zh:a26fdea0afaa9b247c73c0b42843ca51ba7db0ac2571f9d3d50dcabd20ca1b98", + "zh:c872c9385a78d502bf5823d61cd3bb0f9a0585030e025eb12585c83451beeaa1", + "zh:f180879af931182beee4c8c0d9dab62b81d86f17ddcbe3786ef4c7cec9163a4e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f70f5789264069e0eef06f9b5d5fde955ef7206f7d446d1ce51a4c37a3f3e02f", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.9.0" + hashes = [ + "h1:m24fjcInWvTVZ1XSo2MaNuKPe+X/gfG8SIi09rA7a7M=", + "zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0", + "zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64", + "zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90", + "zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c", + "zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8", + "zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8", + "zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b", + "zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9", + "zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195", + "zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3", + "zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.9.0" + constraints = "3.9.0" + hashes = [ + "h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=", + "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", + "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", + "zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f", + "zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0", + "zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61", + "zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc", + "zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e", + "zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef", + "zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b", + "zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257", + "zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.14.0" + constraints = ">= 0.9.1" + hashes = [ + "h1:/hlxsUpuN/lvPTNL9+NyVGsOyRsK5NsxwFMsj5CdOp4=", + "zh:12abfd6b800e4d7fa6db7310dec8ffd440b31993861ef188c7ed5260b3073937", + "zh:23005521e800bb19e1597bf755c5f70d675d30b685d4255001ed5fa47d9df3f1", + "zh:2fea249b582ae97cd1cc10385187ea50993bb47c28cc5df0305e57ceaabf0a10", + "zh:322018d3b987b7aad08697178029a2bb667bed699e88328f0c89c52a2fd41341", + "zh:32a08e98fce2d273cb9b2c89d6c54727cc9f0a32e15bfd896be4e02cc6b48f95", + "zh:3db89aabd0e619616bd4b0f8b373a7586dfe60feffcea12a84a0bdbc445714b3", + "zh:7488f56c81d742dc020f29063626c8f07ca188aa97be61e7307e8d62397020a2", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7cb4067f2e7559b13f7562ef722f948950901eb37834873e98360ab28f66e9d7", + "zh:9d552c8345f61e1b7db8e725144981345f18ac1014d58d6f5ddf0928a195fffb", + "zh:a8e69fb6b97fc9d86fb19a9f4d42abe33c4a68e700b15387ce2e17d2b9934bed", + "zh:aeeb900eb8dd0f790c60ea5c0e0c8d42bd6e4a54f391681d4decca15b544394b", + "zh:c239c619101a8c95e1f14061eb973c57a8d15fa0e68878ced5bbd76858ee5b79", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.96.0" + constraints = ">= 0.87.0, >= 0.95.0" + hashes = [ + "h1:NgwbVCV5pfBVMO3xUMop4l5AzvVv3BuBzXpJjgoZfSU=", + "zh:04d309851424a53d3d014dde3b143fc1cdc19fbebf558eb4b927878103f78fb0", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:0ebcdf98a47f301e12925803198320d637552ef57abc49e2a48a009f1ddbf39a", + "zh:176238c057193c9c60c365b83463e758892186fcc2bd14bc9bbf69bf471f1d6b", + "zh:1c514ec6d09ee210ebb813d49b7d3a71b5b9d0b173c743bce9ab937b1e3d303a", + "zh:20433d0dc7e4aa2a806863fc289a2cecb19763624f199babfbe44f22d4d9150f", + "zh:452ceacbe4a1f70c81320b9223f4958c9bc122508c79e86bc97cb9241682c053", + "zh:5f893229f41f8dc2169b5b02785fb2988e8cad2141722a411711182bafefa015", + "zh:69383e27067a6413300d3acbcdad8f890bd187e16630580c09900ba379659284", + "zh:694de24bd05027c3c8b7a7c477973f76cd5a11d7fd38819026b5a0e588698fd9", + "zh:7c7399e3223dd76efb56ca2e3c9435b41bcbaf549839cec36023f801ca5bdcd2", + "zh:8a92b221694c59648d22e2e2a0059015872eff7034ae0ba9eb801fe399644a2c", + "zh:90a8ae716c9bc6c8804a38f7a903c7af7114ce324d0126c64e1447b6d255cdba", + "zh:d29eb17fde9460c5ce3c7a7975eef0ad7fea692eb17fad5e0421952e4d29dbd2", + ] +} diff --git a/examples/iam-scim-integration/010-provider.tf b/examples/iam-scim-integration/010-provider.tf new file mode 100644 index 0000000..ba5d9ed --- /dev/null +++ b/examples/iam-scim-integration/010-provider.tf @@ -0,0 +1,66 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.95.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">2.14.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.0.0" + } + authentik = { + source = "goauthentik/authentik" + version = "2026.2.0" + } + time = { + source = "hashicorp/time" + version = ">= 0.9.1" + } + } +} + +provider "authentik" { + url = "https://${stackit_dns_record_set.authentik.name}.${stackit_dns_zone.this.dns_name}" + token = random_password.authentik_bootstrap_token.result +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + enable_beta_resources = true +} + + +provider "kubernetes" { + host = yamldecode(module.ske.kubeconfig).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(module.ske.kubeconfig).clusters.0.cluster.certificate-authority-data) +} + +provider "helm" { + kubernetes = { + host = yamldecode(module.ske.kubeconfig).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(module.ske.kubeconfig).clusters.0.cluster.certificate-authority-data) + } +} diff --git a/examples/iam-scim-integration/020-variables.tf b/examples/iam-scim-integration/020-variables.tf new file mode 100644 index 0000000..f6539e9 --- /dev/null +++ b/examples/iam-scim-integration/020-variables.tf @@ -0,0 +1,47 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_project_id" { + type = string +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string +} + +variable "acme_email" { + description = "The email address used for ACME registration." + type = string +} + +variable "authentik_scim_long_lived_token" { + description = "The SCIM synchronization token provided by the IDP team. This configuration uses a long-lived static token due to Authentik Community Edition limitations. For production environments, dynamically generated, short-lived tokens are highly recommended." + type = string +} + +variable "authentik_number_of_users" { + description = "The number of test users to generate" + type = number +} + +variable "authentik_default_user_password" { + description = "The default password assigned to all created test users" + type = string + sensitive = true +} diff --git a/examples/iam-scim-integration/030-ske-cluster.tf b/examples/iam-scim-integration/030-ske-cluster.tf new file mode 100644 index 0000000..7241a13 --- /dev/null +++ b/examples/iam-scim-integration/030-ske-cluster.tf @@ -0,0 +1,37 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "ske" { + source = "../../modules/test-ske" + project_id = var.stackit_project_id + cluster_name = "ske-test" +} + +resource "kubernetes_namespace_v1" "cert_manager" { + metadata { + name = "cert-manager" + } +} + +resource "kubernetes_namespace_v1" "authentik" { + metadata { + name = "authentik" + } +} + +resource "kubernetes_namespace_v1" "nginx" { + metadata { + name = "nginx" + } +} diff --git a/examples/iam-scim-integration/040-dns.tf b/examples/iam-scim-integration/040-dns.tf new file mode 100644 index 0000000..57a1ce6 --- /dev/null +++ b/examples/iam-scim-integration/040-dns.tf @@ -0,0 +1,46 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_public_ip" "ingress_floating_ip" { + project_id = var.stackit_project_id + + lifecycle { + ignore_changes = [network_interface_id] + } +} + +resource "random_string" "this" { + length = 6 + special = false + upper = false +} + +resource "stackit_dns_zone" "this" { + project_id = var.stackit_project_id + name = random_string.this.result + dns_name = "${random_string.this.result}.runs.onstackit.cloud" + type = "primary" + default_ttl = 60 + contact_email = "hostmaster@stackit.cloud" +} + +resource "stackit_dns_record_set" "authentik" { + project_id = var.stackit_project_id + zone_id = stackit_dns_zone.this.zone_id + name = "authentik" + type = "A" + ttl = 60 + comment = "a record" + records = [stackit_public_ip.ingress_floating_ip.ip] +} diff --git a/examples/iam-scim-integration/050-cert-manager.tf b/examples/iam-scim-integration/050-cert-manager.tf new file mode 100644 index 0000000..0cf6ac9 --- /dev/null +++ b/examples/iam-scim-integration/050-cert-manager.tf @@ -0,0 +1,62 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "helm_release" "cert_manager" { + name = "cert-manager" + repository = "https://charts.jetstack.io" + chart = "cert-manager" + version = "v1.15.1" + + timeout = 120 + cleanup_on_fail = true + force_update = false + namespace = kubernetes_namespace_v1.cert_manager.metadata.0.name + + set = [ + { + name = "crds.enabled" + value = "true" + } + ] +} + +resource "kubernetes_manifest" "cluster_issuer" { + manifest = { + apiVersion = "cert-manager.io/v1" + kind = "ClusterIssuer" + metadata = { + name = "letsencrypt-prod-cluster" + } + spec = { + acme = { + email = var.acme_email + server = "https://acme-v02.api.letsencrypt.org/directory" + privateKeySecretRef = { + name = "letsencrypt-prod-cluster" + } + solvers = [ + { + http01 = { + ingress = { + class = "nginx" + } + } + } + ] + } + } + } + + depends_on = [helm_release.cert_manager] +} diff --git a/examples/iam-scim-integration/060-nginx-ingress.tf b/examples/iam-scim-integration/060-nginx-ingress.tf new file mode 100644 index 0000000..4aa4624 --- /dev/null +++ b/examples/iam-scim-integration/060-nginx-ingress.tf @@ -0,0 +1,36 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "helm_release" "nginx_ingress" { + name = "nginx-ingress" + repository = "https://kubernetes.github.io/ingress-nginx" + chart = "ingress-nginx" + version = "4.2.3" + + namespace = kubernetes_namespace_v1.nginx.metadata.0.name + + values = [ + < `sub` + - Email address -> `email` + - Preferred name -> `preferred_username` + - First name -> `given_name` + - Last name -> `family_name` + +### What you will receive in return: + +Once STACKIT support processes your ticket, they will configure the trust relationship on their end. You will receive: + +1. **Confirmation of Federation:** Your Authentik instance will officially be trusted by the STACKIT login portal. +2. **SCIM Credentials:** You will be provided with the required OAuth credentials to generate the necessary Bearer tokens so Authentik can communicate with the STACKIT SCIM API. + +--- + +## Testing the SCIM Integration + +### Scenario 1: User Sync + +1. **Create a User**: In the Authentik UI (_Directory -> Users_), create a new test user. +2. **Assign to Application**: Ensure the user is assigned to the `STACKIT` application. +3. **Verify**: Log in to the STACKIT Portal. If the user doesn't appear immediately, go to _Applications -> STACKIT -> Backchannel Providers_ and click **Sync Now**. + +### Scenario 2: Group & Role Mapping (RBAC) + +1. **Create/Assign Group**: Add your user to the `stackit-admins` group in Authentik. +2. **Map to STACKIT Role**: In the STACKIT Org settings, map this group to the `Owner` or `Admin` role. +3. **Verify Access**: + - Log in to the STACKIT Portal. The user should have the assigned organization-level permissions. + - **Remove Group**: Remove the user from the group in Authentik. After sync, the user's permissions in the STACKIT Org will be revoked. + +--- + +## Visual Verification + +### 1. Dashboard/Application Overview + +![Dashboard](docs/authentik-dashboard-overview.png) +![Application](docs/authentik-application-overview.png) + +### 2. User & Group Management + +![Groups](docs/authentik-user-management.png) +![Provider](docs/authentik-group-management.png) + +### 3. SCIM Sync + +![Scim](docs/authentik-scim-sync.png) + +### 4. Group on STACKIT Side + +![Stackit-group-sync](docs/search-for-group-stackit-admins.png) + +--- + +## References & Documentation + +- [Generic OIDC 2.0 Federation Guide](https://docs.stackit.cloud/platform/access-and-identity/stackit-idp/how-tos/generic-oidc-2_0-federation-guide/) +- [SCIM Endpoint STACKIT IdP Guide](https://docs.stackit.cloud/platform/access-and-identity/stackit-idp/how-tos/scim-endpoint/) diff --git a/examples/iam-scim-integration/docs/authentik-application-overview.png b/examples/iam-scim-integration/docs/authentik-application-overview.png new file mode 100644 index 0000000..ccb2fc9 Binary files /dev/null and b/examples/iam-scim-integration/docs/authentik-application-overview.png differ diff --git a/examples/iam-scim-integration/docs/authentik-dashboard-overview.png b/examples/iam-scim-integration/docs/authentik-dashboard-overview.png new file mode 100644 index 0000000..5d233e1 Binary files /dev/null and b/examples/iam-scim-integration/docs/authentik-dashboard-overview.png differ diff --git a/examples/iam-scim-integration/docs/authentik-group-management.png b/examples/iam-scim-integration/docs/authentik-group-management.png new file mode 100644 index 0000000..19b67f9 Binary files /dev/null and b/examples/iam-scim-integration/docs/authentik-group-management.png differ diff --git a/examples/iam-scim-integration/docs/authentik-scim-sync.png b/examples/iam-scim-integration/docs/authentik-scim-sync.png new file mode 100644 index 0000000..881e612 Binary files /dev/null and b/examples/iam-scim-integration/docs/authentik-scim-sync.png differ diff --git a/examples/iam-scim-integration/docs/authentik-user-management.png b/examples/iam-scim-integration/docs/authentik-user-management.png new file mode 100644 index 0000000..e4b588c Binary files /dev/null and b/examples/iam-scim-integration/docs/authentik-user-management.png differ diff --git a/examples/iam-scim-integration/docs/search-for-group-stackit-admins.png b/examples/iam-scim-integration/docs/search-for-group-stackit-admins.png new file mode 100644 index 0000000..e528aa2 Binary files /dev/null and b/examples/iam-scim-integration/docs/search-for-group-stackit-admins.png differ diff --git a/examples/pfsense-hub-and-spoke/cloud-init/user-init-linux.yml b/examples/pfsense-hub-and-spoke/cloud-init/user-init-linux.yml index 6850de0..be6e579 100644 --- a/examples/pfsense-hub-and-spoke/cloud-init/user-init-linux.yml +++ b/examples/pfsense-hub-and-spoke/cloud-init/user-init-linux.yml @@ -8,7 +8,7 @@ # default password in production. # # Generate a SHA-512 hash on Linux/macOS: -# python3 -c "import crypt; print(crypt.crypt('YourPassword', crypt.mksalt(crypt.METHOD_SHA512)))" +# openssl passwd -6 "YourPassword" # --------------------------------------------------------------------------- users: - name: admin-user diff --git a/examples/resourcemanager-nested-folders/.terraform.lock.hcl b/examples/resourcemanager-nested-folders/.terraform.lock.hcl index c9888a0..0fdab96 100644 --- a/examples/resourcemanager-nested-folders/.terraform.lock.hcl +++ b/examples/resourcemanager-nested-folders/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/random" { version = "3.6.3" constraints = "3.6.3" hashes = [ + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", "h1:zG9uFP8l9u+yGZZvi5Te7PV62j50azpgwPunq2vTm1E=", "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", @@ -25,6 +26,7 @@ provider "registry.terraform.io/stackitcloud/stackit" { version = "0.90.0" constraints = ">= 0.66.0" hashes = [ + "h1:QgP6TOtucJ3A6fA51rdUvxhYGjl9RrWvXQZpjHTOuiU=", "h1:W29Kv6XUxYssF2Gy8KcmTx3EFstt6k8sKgPRIBbq+qs=", "zh:003af58a84884558bbb2fc40fcbefa6774ec20aa9e4b97cf3f950190a600afd2", "zh:026ee9cef4670cf33369f8654c6b9b1d8c0e116ceb0b353c882be222951ecdd4", diff --git a/examples/resourcemanager-nested-folders/01-variables.tf b/examples/resourcemanager-nested-folders/01-variables.tf index 6170b51..7c85cff 100644 --- a/examples/resourcemanager-nested-folders/01-variables.tf +++ b/examples/resourcemanager-nested-folders/01-variables.tf @@ -24,7 +24,7 @@ variable "stackit_service_account_key_path" { variable "stackit_org_id" { type = string - default = "03a34540-3c1a-4794-b2c6-7111ecf824ef" + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } variable "owner_email" { diff --git a/examples/resourcemanager-nested-folders/README.md b/examples/resourcemanager-nested-folders/README.md index 1e99e45..b3d356d 100644 --- a/examples/resourcemanager-nested-folders/README.md +++ b/examples/resourcemanager-nested-folders/README.md @@ -2,4 +2,6 @@ ## Overview +> ⚠️ Two levels of folders must be enabled via a support ticket. By default, only one level is possible. + This repository demonstrates code to generate nested folders within a project. diff --git a/examples/s3-aws-terraform-provider/.terraform.lock.hcl b/examples/s3-aws-terraform-provider/.terraform.lock.hcl new file mode 100644 index 0000000..8873576 --- /dev/null +++ b/examples/s3-aws-terraform-provider/.terraform.lock.hcl @@ -0,0 +1,47 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.100.0" + constraints = "~> 5.0" + hashes = [ + "h1:edXOJWE4ORX8Fm+dpVpICzMZJat4AX0VRCAy/xkcOc0=", + "zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644", + "zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2", + "zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274", + "zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b", + "zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862", + "zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93", + "zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2", + "zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e", + "zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421", + "zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4", + "zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9", + "zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9", + "zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.94.0" + constraints = "> 0.90.0" + hashes = [ + "h1:ikFzd4yeJ1LR8ojP2PsZwiK2ZLhxBjRXkEg2HJrI07U=", + "zh:06c8da7d8a048216e825fa7d1e45949c1bda2a5f53f9bb0556b83b6610703fe6", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:19e82636cfd52a65105e0cf030bc8a0c815082818ef953b84f9b1e349a87318c", + "zh:24af9b7d2f1bb38f480b1aa8cf5e4ecf483bd4403642a9e8a5accbe1ae212feb", + "zh:3b10850e9242bcd00c519ff4140130e8443002fd60b6dff90983e7cb1973b2c3", + "zh:54837a0fa4ddbcf0b8407718f8823b831322deba3bd7ec8492e4578928f50633", + "zh:5cfd6a6b1ca73826a03f8746ef84a5c4059648bc49abf8056c8e0f9b87800a23", + "zh:6ab3bcfef6ff65b4ce76d333b4ad99e5f91991fcf5bddbe1958aadde6ee05eab", + "zh:81b96dc29b055f15e475d8bc32482617a582785949b3c02f44ef15d19951f69c", + "zh:85f478c2fcf10219263462d0f06b5cc41603b1edad813c336e100b3e0a55bfe8", + "zh:9adbb7655fddfe4d4081746d0d7e39c3e8fbf8aa3d8b7d3b5164f30c16a6bd93", + "zh:9c24b39e788283ead8a8ce1f013a47562ff0dc1ccb642a8e18644cbdcda0f1c4", + "zh:a425f28d6a5f6f024cab56c848c55025e84a09db946f1b00a2655d9567251cea", + "zh:f28aa62d2f06e08fe6d18ef9103a8164aa9278540779bebd61120f810c603c6b", + ] +} diff --git a/examples/secretsmanager-vault-terraform-provider/.terraform.lock.hcl b/examples/secretsmanager-vault-terraform-provider/.terraform.lock.hcl new file mode 100644 index 0000000..474bc20 --- /dev/null +++ b/examples/secretsmanager-vault-terraform-provider/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/vault" { + version = "5.9.0" + constraints = "5.9.0" + hashes = [ + "h1:8wcXxEMo7XvCnrtZHSpAuWmRfYiZkWn2tssshB1BDzo=", + "zh:16e23a37c0965938544af282a7bc13dabca445f462ab27829f98e936ace4d263", + "zh:249fcf9da1a690fe9aa44a7421fad89a425afb0c2ce7eaf306d75daddd691af5", + "zh:3d92af386049a229a428f21b938a22df61703447c8ceed65c73f111a64e627d2", + "zh:4033fedf9d4f54f0aacf7c4a79e20978bcd67c0a8ab9411acd447db1469108a4", + "zh:51c78d0dc378037bbaf3cd26ff29fae7c40d7b134b40d059b982257987c15f9f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:86e414b7327343de676ec506d30c557a514dbd992b27a2670466adaf9ed69718", + "zh:879c3a61ed8d183a68ddb590e63a7e0d6aab8d8044fd4a13658e7b1661395a9d", + "zh:8d548617543ee2ce0340972a5df93e7ac37b7895d4bf506bd587f8daac58e6d6", + "zh:8d75b3bbfd9a536c8c1d84504cb3d1c8e1a3fd30e377a51a6311476632363103", + "zh:922f625a36642c49daa432e07c12e72ff75025e0b9afda8d7240f38c6789fe46", + "zh:fbceae685b395acaff6c820ed7d7eaa6250ef4769e04481145dc50e09b89db2f", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.94.0" + constraints = ">= 0.94.0" + hashes = [ + "h1:ikFzd4yeJ1LR8ojP2PsZwiK2ZLhxBjRXkEg2HJrI07U=", + "zh:06c8da7d8a048216e825fa7d1e45949c1bda2a5f53f9bb0556b83b6610703fe6", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:19e82636cfd52a65105e0cf030bc8a0c815082818ef953b84f9b1e349a87318c", + "zh:24af9b7d2f1bb38f480b1aa8cf5e4ecf483bd4403642a9e8a5accbe1ae212feb", + "zh:3b10850e9242bcd00c519ff4140130e8443002fd60b6dff90983e7cb1973b2c3", + "zh:54837a0fa4ddbcf0b8407718f8823b831322deba3bd7ec8492e4578928f50633", + "zh:5cfd6a6b1ca73826a03f8746ef84a5c4059648bc49abf8056c8e0f9b87800a23", + "zh:6ab3bcfef6ff65b4ce76d333b4ad99e5f91991fcf5bddbe1958aadde6ee05eab", + "zh:81b96dc29b055f15e475d8bc32482617a582785949b3c02f44ef15d19951f69c", + "zh:85f478c2fcf10219263462d0f06b5cc41603b1edad813c336e100b3e0a55bfe8", + "zh:9adbb7655fddfe4d4081746d0d7e39c3e8fbf8aa3d8b7d3b5164f30c16a6bd93", + "zh:9c24b39e788283ead8a8ce1f013a47562ff0dc1ccb642a8e18644cbdcda0f1c4", + "zh:a425f28d6a5f6f024cab56c848c55025e84a09db946f1b00a2655d9567251cea", + "zh:f28aa62d2f06e08fe6d18ef9103a8164aa9278540779bebd61120f810c603c6b", + ] +} diff --git a/examples/secretsmanager-vault-terraform-provider/main.tf b/examples/secretsmanager-vault-terraform-provider/main.tf index ddd4b6e..bea426c 100644 --- a/examples/secretsmanager-vault-terraform-provider/main.tf +++ b/examples/secretsmanager-vault-terraform-provider/main.tf @@ -12,11 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -provider "stackit" { - default_region = "eu01" - service_account_key_path = "" -} - resource "stackit_secretsmanager_instance" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example-instance" @@ -29,16 +24,6 @@ resource "stackit_secretsmanager_user" "example" { write_enabled = true } -provider "vault" { - address = "https://prod.sm.eu01.stackit.cloud" - skip_child_token = true - - auth_login_userpass { - username = stackit_secretsmanager_user.example.username - password = stackit_secretsmanager_user.example.password - } -} - resource "stackit_observability_instance" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example-instance" diff --git a/examples/secretsmanager-vault-terraform-provider/provider.tf b/examples/secretsmanager-vault-terraform-provider/provider.tf new file mode 100644 index 0000000..cf73ad6 --- /dev/null +++ b/examples/secretsmanager-vault-terraform-provider/provider.tf @@ -0,0 +1,41 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.94.0" + } + vault = { + source = "hashicorp/vault" + version = "5.9.0" + } + } +} + +provider "stackit" { + default_region = "eu01" + service_account_key_path = "" +} + +provider "vault" { + address = "https://prod.sm.eu01.stackit.cloud" + skip_child_token = true + + auth_login_userpass { + username = stackit_secretsmanager_user.example.username + password = stackit_secretsmanager_user.example.password + } +} diff --git a/examples/ske-azure-arc-integration/.terraform.lock.hcl b/examples/ske-azure-arc-integration/.terraform.lock.hcl new file mode 100644 index 0000000..83474cf --- /dev/null +++ b/examples/ske-azure-arc-integration/.terraform.lock.hcl @@ -0,0 +1,104 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "4.72.0" + constraints = "4.72.0" + hashes = [ + "h1:QYnPAHT/PYheOOZz52ucHqw/ZO9PxWyPLtO7UD/jSMg=", + "zh:073472587c3752e89738522814d2b4eb2fd69eb2cb19c5a5ead3c7d2eabdc279", + "zh:1950effc0c315b6002c8cb6327b94fe59bda210e699367d9727bc66490d651d2", + "zh:47c990db75658525de57c8955a05b4752b88f3a900fffac0e7661d4a749e94f2", + "zh:610f2cbd6fab76750d8b093f03beabbb7162dc8c6affe0109f534ce240b3ff0f", + "zh:6739d645fe548c5a489d711f7748f32368cf68d723d2c59d3f2e21456304d692", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a277ab095cc8aff3aede9e43eca2a699936472ef90abb272adf3daa609eb9141", + "zh:b1fdcdaf926c86de0d884beda90d78cb94a42ddede03a1f0b92c36b321d4f07e", + "zh:c003f1f15e52c54e189301ae2c7d8dd65acb2e5a7527d201355f2757b5465ba9", + "zh:c45f2d2206c0f8f71f207cd39eec73da9619d35932bbe1a5b8be7679c50a151e", + "zh:d7040d8ec295481bc1d30346ed7f3075c40ede87c0fedf1db34dd91c1c367a10", + "zh:e595f0b870cd5fd5debdc926fc1740201d2b66188b9b132dc598bdd6444e7348", + ] +} + +provider "registry.terraform.io/hashicorp/helm" { + version = "3.1.1" + hashes = [ + "h1:47CqNwkxctJtL/N/JuEj+8QMg8mRNI/NWeKO5/ydfZU=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.1.0" + constraints = "> 2.14.0" + hashes = [ + "h1:G9QqKNpcztBRqrywtlNylFJSpGzDfRFtO8hcWLdkvRY=", + "zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0", + "zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20", + "zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c", + "zh:1678c982853ad461e65ccb5e79d585e13ed109dd47dab2a66d3a7a304faeef65", + "zh:1c050a5c15e330457a9c18caacf61a923c59d663e13f2962e4b32f04fef523a0", + "zh:2c55bcec83be58ec132c7cb0a1ac644758b800d794fdc636d53a0eada0358a3a", + "zh:a062bb0aa316c08d8460c66a5d68da71da40de5d3bc3b31abcf3a1a9a19650f1", + "zh:a26fdea0afaa9b247c73c0b42843ca51ba7db0ac2571f9d3d50dcabd20ca1b98", + "zh:c872c9385a78d502bf5823d61cd3bb0f9a0585030e025eb12585c83451beeaa1", + "zh:f180879af931182beee4c8c0d9dab62b81d86f17ddcbe3786ef4c7cec9163a4e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f70f5789264069e0eef06f9b5d5fde955ef7206f7d446d1ce51a4c37a3f3e02f", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.9.0" + constraints = "3.9.0" + hashes = [ + "h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=", + "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", + "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", + "zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f", + "zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0", + "zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61", + "zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc", + "zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e", + "zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef", + "zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b", + "zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257", + "zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.96.0" + constraints = ">= 0.95.0" + hashes = [ + "h1:NgwbVCV5pfBVMO3xUMop4l5AzvVv3BuBzXpJjgoZfSU=", + "zh:04d309851424a53d3d014dde3b143fc1cdc19fbebf558eb4b927878103f78fb0", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:0ebcdf98a47f301e12925803198320d637552ef57abc49e2a48a009f1ddbf39a", + "zh:176238c057193c9c60c365b83463e758892186fcc2bd14bc9bbf69bf471f1d6b", + "zh:1c514ec6d09ee210ebb813d49b7d3a71b5b9d0b173c743bce9ab937b1e3d303a", + "zh:20433d0dc7e4aa2a806863fc289a2cecb19763624f199babfbe44f22d4d9150f", + "zh:452ceacbe4a1f70c81320b9223f4958c9bc122508c79e86bc97cb9241682c053", + "zh:5f893229f41f8dc2169b5b02785fb2988e8cad2141722a411711182bafefa015", + "zh:69383e27067a6413300d3acbcdad8f890bd187e16630580c09900ba379659284", + "zh:694de24bd05027c3c8b7a7c477973f76cd5a11d7fd38819026b5a0e588698fd9", + "zh:7c7399e3223dd76efb56ca2e3c9435b41bcbaf549839cec36023f801ca5bdcd2", + "zh:8a92b221694c59648d22e2e2a0059015872eff7034ae0ba9eb801fe399644a2c", + "zh:90a8ae716c9bc6c8804a38f7a903c7af7114ce324d0126c64e1447b6d255cdba", + "zh:d29eb17fde9460c5ce3c7a7975eef0ad7fea692eb17fad5e0421952e4d29dbd2", + ] +} diff --git a/examples/ske-azure-arc-integration/010-provider.tf b/examples/ske-azure-arc-integration/010-provider.tf new file mode 100644 index 0000000..871e78b --- /dev/null +++ b/examples/ske-azure-arc-integration/010-provider.tf @@ -0,0 +1,57 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.95.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">2.14.0" + } + azurerm = { + source = "hashicorp/azurerm" + version = "4.72.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + enable_beta_resources = true +} + +provider "azurerm" { + features {} + subscription_id = var.azure_subscription_id +} + +provider "kubernetes" { + host = yamldecode(module.ske.kubeconfig).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(module.ske.kubeconfig).clusters.0.cluster.certificate-authority-data) +} + +provider "helm" { + kubernetes = { + host = yamldecode(module.ske.kubeconfig).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(module.ske.kubeconfig).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(module.ske.kubeconfig).clusters.0.cluster.certificate-authority-data) + } +} diff --git a/examples/ske-azure-arc-integration/020-variables.tf b/examples/ske-azure-arc-integration/020-variables.tf new file mode 100644 index 0000000..ccc5d37 --- /dev/null +++ b/examples/ske-azure-arc-integration/020-variables.tf @@ -0,0 +1,30 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_project_id" { + type = string +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string +} + +variable "azure_subscription_id" { + type = string +} diff --git a/examples/ske-azure-arc-integration/030-stackit-azure-arc.tf b/examples/ske-azure-arc-integration/030-stackit-azure-arc.tf new file mode 100644 index 0000000..3a99206 --- /dev/null +++ b/examples/ske-azure-arc-integration/030-stackit-azure-arc.tf @@ -0,0 +1,23 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "ske" { + source = "../../modules/test-ske" + project_id = var.stackit_project_id +} + +resource "azurerm_resource_group" "arc_rg" { + name = "rg-stackit-arc-poc" + location = "West Europe" +} diff --git a/examples/ske-azure-arc-integration/040-outputs.tf b/examples/ske-azure-arc-integration/040-outputs.tf new file mode 100644 index 0000000..bdd2b97 --- /dev/null +++ b/examples/ske-azure-arc-integration/040-outputs.tf @@ -0,0 +1,30 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +output "kubeconfig" { + value = module.ske.kubeconfig + sensitive = true +} + +output "cluster_name" { + value = module.ske.cluster_name +} + +output "azure_resource_group" { + value = azurerm_resource_group.arc_rg.name +} + +output "azure_location" { + value = azurerm_resource_group.arc_rg.location +} diff --git a/examples/ske-azure-arc-integration/MAINTAINERS.md b/examples/ske-azure-arc-integration/MAINTAINERS.md new file mode 100644 index 0000000..1aaefce --- /dev/null +++ b/examples/ske-azure-arc-integration/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (mauritz.uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/ske-azure-arc-integration/README.md b/examples/ske-azure-arc-integration/README.md new file mode 100644 index 0000000..391679b --- /dev/null +++ b/examples/ske-azure-arc-integration/README.md @@ -0,0 +1,46 @@ +# STACKIT SKE Azure Arc Integration + +This repository contains Terraform and CLI steps to connect a **STACKIT SKE cluster** to **Azure Arc**. + +## Prerequisites + +- Azure CLI installed and authenticated (`az login`) +- Terraform installed +- STACKIT Project & Service Account configured + +## Setup Guide + +### 1. Provision Infrastructure + +Deploy the SKE cluster and an Azure Resource Group to host the Arc connection: + +```bash +terraform init +terraform apply +``` + +### 2. Connect to Azure Arc + +Run the following commands to register required Azure providers and connect the cluster: + +```bash +# Register Azure Arc providers +az extension add --name connectedk8s +az provider register --namespace Microsoft.Kubernetes +az provider register --namespace Microsoft.KubernetesConfiguration +az provider register --namespace Microsoft.ExtendedLocation + +# Export SKE Kubeconfig +terraform output -raw kubeconfig > .kubeconfig + +# Connect cluster to Azure Arc +az connectedk8s connect \ + --name "stackit-$(terraform output -raw cluster_name)" \ + --resource-group "$(terraform output -raw azure_resource_group)" \ + --location "$(terraform output -raw azure_location)" \ + --kube-config .kubeconfig +``` + +## References + +- [Azure Arc Quickstart](https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/quickstart-connect-cluster?tabs=azure-cli) diff --git a/examples/ske-encrypted-volumes/.terraform.lock.hcl b/examples/ske-encrypted-volumes/.terraform.lock.hcl new file mode 100644 index 0000000..39718fe --- /dev/null +++ b/examples/ske-encrypted-volumes/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.1.0" + constraints = ">= 3.1.0" + hashes = [ + "h1:oodIAuFMikXNmEtil5MQgP4dfSctUBYQiGJfjbsF3NY=", + "zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0", + "zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20", + "zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c", + "zh:1678c982853ad461e65ccb5e79d585e13ed109dd47dab2a66d3a7a304faeef65", + "zh:1c050a5c15e330457a9c18caacf61a923c59d663e13f2962e4b32f04fef523a0", + "zh:2c55bcec83be58ec132c7cb0a1ac644758b800d794fdc636d53a0eada0358a3a", + "zh:a062bb0aa316c08d8460c66a5d68da71da40de5d3bc3b31abcf3a1a9a19650f1", + "zh:a26fdea0afaa9b247c73c0b42843ca51ba7db0ac2571f9d3d50dcabd20ca1b98", + "zh:c872c9385a78d502bf5823d61cd3bb0f9a0585030e025eb12585c83451beeaa1", + "zh:f180879af931182beee4c8c0d9dab62b81d86f17ddcbe3786ef4c7cec9163a4e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f70f5789264069e0eef06f9b5d5fde955ef7206f7d446d1ce51a4c37a3f3e02f", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.94.0" + constraints = ">= 0.94.0" + hashes = [ + "h1:ikFzd4yeJ1LR8ojP2PsZwiK2ZLhxBjRXkEg2HJrI07U=", + "zh:06c8da7d8a048216e825fa7d1e45949c1bda2a5f53f9bb0556b83b6610703fe6", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:19e82636cfd52a65105e0cf030bc8a0c815082818ef953b84f9b1e349a87318c", + "zh:24af9b7d2f1bb38f480b1aa8cf5e4ecf483bd4403642a9e8a5accbe1ae212feb", + "zh:3b10850e9242bcd00c519ff4140130e8443002fd60b6dff90983e7cb1973b2c3", + "zh:54837a0fa4ddbcf0b8407718f8823b831322deba3bd7ec8492e4578928f50633", + "zh:5cfd6a6b1ca73826a03f8746ef84a5c4059648bc49abf8056c8e0f9b87800a23", + "zh:6ab3bcfef6ff65b4ce76d333b4ad99e5f91991fcf5bddbe1958aadde6ee05eab", + "zh:81b96dc29b055f15e475d8bc32482617a582785949b3c02f44ef15d19951f69c", + "zh:85f478c2fcf10219263462d0f06b5cc41603b1edad813c336e100b3e0a55bfe8", + "zh:9adbb7655fddfe4d4081746d0d7e39c3e8fbf8aa3d8b7d3b5164f30c16a6bd93", + "zh:9c24b39e788283ead8a8ce1f013a47562ff0dc1ccb642a8e18644cbdcda0f1c4", + "zh:a425f28d6a5f6f024cab56c848c55025e84a09db946f1b00a2655d9567251cea", + "zh:f28aa62d2f06e08fe6d18ef9103a8164aa9278540779bebd61120f810c603c6b", + ] +} diff --git a/examples/ske-encrypted-volumes/main.tf b/examples/ske-encrypted-volumes/main.tf index 8e2d010..55d2826 100644 --- a/examples/ske-encrypted-volumes/main.tf +++ b/examples/ske-encrypted-volumes/main.tf @@ -12,18 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -provider "stackit" { - default_region = "eu01" - service_account_key_path = "" -} - -provider "kubernetes" { - host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server - client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) - client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) - cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) -} - resource "stackit_ske_cluster" "default" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "ske-enc-vol" diff --git a/examples/ske-encrypted-volumes/provider.tf b/examples/ske-encrypted-volumes/provider.tf new file mode 100644 index 0000000..7c46d2b --- /dev/null +++ b/examples/ske-encrypted-volumes/provider.tf @@ -0,0 +1,38 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.94.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 3.1.0" + } + } +} + +provider "stackit" { + default_region = "eu01" + service_account_key_path = "" +} + +provider "kubernetes" { + host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) +} diff --git a/examples/ske-external-secrets-sync/.terraform.lock.hcl b/examples/ske-external-secrets-sync/.terraform.lock.hcl index fbb7035..f7f082b 100644 --- a/examples/ske-external-secrets-sync/.terraform.lock.hcl +++ b/examples/ske-external-secrets-sync/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/helm" { version = "2.17.0" constraints = "2.17.0" hashes = [ + "h1:K5FEjxvDnxb1JF1kG1xr8J3pNGxoaR3Z0IBG9Csm/Is=", "h1:kQMkcPVvHOguOqnxoEU2sm1ND9vCHiT8TvZ2x6v/Rsw=", "zh:06fb4e9932f0afc1904d2279e6e99353c2ddac0d765305ce90519af410706bd4", "zh:104eccfc781fc868da3c7fec4385ad14ed183eb985c96331a1a937ac79c2d1a7", @@ -26,6 +27,7 @@ provider "registry.terraform.io/hashicorp/kubernetes" { constraints = ">= 2.25.2" hashes = [ "h1:P0c8knzZnouTNFIRij8IS7+pqd0OKaFDYX0j4GRsiqo=", + "h1:vyHdH0p6bf9xp1NPePObAJkXTJb/I09FQQmmevTzZe0=", "zh:02d55b0b2238fd17ffa12d5464593864e80f402b90b31f6e1bd02249b9727281", "zh:20b93a51bfeed82682b3c12f09bac3031f5bdb4977c47c97a042e4df4fb2f9ba", "zh:6e14486ecfaee38c09ccf33d4fdaf791409f90795c1b66e026c226fad8bc03c7", @@ -45,6 +47,7 @@ provider "registry.terraform.io/hashicorp/random" { version = "3.7.2" constraints = "3.7.2" hashes = [ + "h1:356j/3XnXEKr9nyicLUufzoF4Yr6hRy481KIxRVpK0c=", "h1:KG4NuIBl1mRWU0KD/BGfCi1YN/j3F7H4YgeeM7iSdNs=", "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", @@ -64,6 +67,7 @@ provider "registry.terraform.io/hashicorp/random" { provider "registry.terraform.io/hashicorp/vault" { version = "5.8.0" hashes = [ + "h1:eSJgYoJoVMce2xjJJCeAZnJELsC4RoqaotD0fgfn6dw=", "h1:gk1cR+x1D+TEz05MKWmpp0p06+Trob5cN0eYU1vZGJs=", "zh:18e79b42c8c155a5c541a45d54a6ccdeab23c404c239acdeed336a17cbfc2fd4", "zh:241f50d1ea40030578034b4440e41676f1c9b5e8a2be5cd3afdb6e387914e0bf", @@ -84,6 +88,7 @@ provider "registry.terraform.io/stackitcloud/stackit" { version = "0.90.0" constraints = ">= 0.66.0" hashes = [ + "h1:QgP6TOtucJ3A6fA51rdUvxhYGjl9RrWvXQZpjHTOuiU=", "h1:W29Kv6XUxYssF2Gy8KcmTx3EFstt6k8sKgPRIBbq+qs=", "zh:003af58a84884558bbb2fc40fcbefa6774ec20aa9e4b97cf3f950190a600afd2", "zh:026ee9cef4670cf33369f8654c6b9b1d8c0e116ceb0b353c882be222951ecdd4", diff --git a/examples/ske-external-secrets-sync/020-variables.tf b/examples/ske-external-secrets-sync/020-variables.tf index 360890d..f87f3ab 100644 --- a/examples/ske-external-secrets-sync/020-variables.tf +++ b/examples/ske-external-secrets-sync/020-variables.tf @@ -14,7 +14,7 @@ variable "stackit_project_id" { type = string - default = "d75e6aab-b616-4b42-ae3b-aaf161ad626d" + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } variable "stackit_region" { diff --git a/examples/ske-external-secrets-sync/050-random-secret.tf b/examples/ske-external-secrets-sync/050-random-secret.tf index 9d61ce3..f158042 100644 --- a/examples/ske-external-secrets-sync/050-random-secret.tf +++ b/examples/ske-external-secrets-sync/050-random-secret.tf @@ -24,11 +24,12 @@ resource "vault_kv_secret_v2" "random_secret" { name = "random-secret" cas = 1 delete_all_versions = true - data_json = jsonencode( + data_json_wo = jsonencode( { admin = ephemeral.random_password.this.result } ) + data_json_wo_version = 1 depends_on = [stackit_secretsmanager_user.user] } diff --git a/examples/ske-gpu-operator/.terraform.lock.hcl b/examples/ske-gpu-operator/.terraform.lock.hcl new file mode 100644 index 0000000..9772a39 --- /dev/null +++ b/examples/ske-gpu-operator/.terraform.lock.hcl @@ -0,0 +1,66 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/helm" { + version = "3.1.1" + hashes = [ + "h1:47CqNwkxctJtL/N/JuEj+8QMg8mRNI/NWeKO5/ydfZU=", + "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.0.1" + constraints = ">= 2.14.0" + hashes = [ + "h1:P0c8knzZnouTNFIRij8IS7+pqd0OKaFDYX0j4GRsiqo=", + "h1:vyHdH0p6bf9xp1NPePObAJkXTJb/I09FQQmmevTzZe0=", + "zh:02d55b0b2238fd17ffa12d5464593864e80f402b90b31f6e1bd02249b9727281", + "zh:20b93a51bfeed82682b3c12f09bac3031f5bdb4977c47c97a042e4df4fb2f9ba", + "zh:6e14486ecfaee38c09ccf33d4fdaf791409f90795c1b66e026c226fad8bc03c7", + "zh:8d0656ff422df94575668e32c310980193fccb1c28117e5c78dd2d4050a760a6", + "zh:9795119b30ec0c1baa99a79abace56ac850b6e6fbce60e7f6067792f6eb4b5f4", + "zh:b388c87acc40f6bd9620f4e23f01f3c7b41d9b88a68d5255dec0a72f0bdec249", + "zh:b59abd0a980649c2f97f172392f080eaeb18e486b603f83bf95f5d93aeccc090", + "zh:ba6e3060fddf4a022087d8f09e38aa0001c705f21170c2ded3d1c26c12f70d97", + "zh:c12626d044b1d5501cf95ca78cbe507c13ad1dd9f12d4736df66eb8e5f336eb8", + "zh:c55203240d50f4cdeb3df1e1760630d677679f5b1a6ffd9eba23662a4ad05119", + "zh:ea206a5a32d6e0d6e32f1849ad703da9a28355d9c516282a8458b5cf1502b2a1", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.91.0" + constraints = ">= 0.60.0" + hashes = [ + "h1:8de9n+Roq6Z2Ltp9poBBBN9a4zSpx73VLpgFS5mTyoI=", + "h1:RStdHSDwbtonYfg7mR5Y92v6fxIVX9FEz0UN+tm9kHI=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:0ed12db90276ccd2d6f87135b7dd078657823c3ca33121c6a157d0bdf08f801e", + "zh:160b32bcf1d01666784cf8469e10e0a38d4c3d24c80c0c5be470cc63ef27ea62", + "zh:32e1909037235c24138b74131c6fb12ac99003f79750f1768ca5468cc05da6b0", + "zh:4376f1cdafbb35ad5f220e28153741908390b23161d9eae3828f7830039ce8ef", + "zh:458b054781ef6165d9136fc3d667f9bf37319e37d0f19300bbb63b703de2599d", + "zh:54a1864cf1315a118c043f834e02f2a1ca0ecbc8c2a246460589a95847da6c80", + "zh:83424712926ccef3c60cc011dfa298721bdbaee3598a0c8459da46bc6b7424cc", + "zh:a3c38ebffdbca21dd177b06acf891bed1a903907ba252d0219d91ff0ecf9d861", + "zh:c6325e583b77aa1e9df94e3b4b12479d7bf12c66a2ace71c1b8f64e46ac5c37e", + "zh:de6db8deeee895af5670df2449c8b8c34df051277f8a6e2f19c5c9ec1f0ddb12", + "zh:e18b05e7d8356caa6103c5c80b5ea373be3ff255b453cf577c68798ffe1b93ce", + "zh:f4d9215f7a2888c882892642539b2edd3ea97cb25904e4fa358db4f001c3ccd0", + "zh:f94d0c0c2bf843867122ababc8d8066d52257e68bbcb5c62a603f77c581e9668", + ] +} diff --git a/examples/ske-gpu-operator/MAINTAINERS.md b/examples/ske-gpu-operator/MAINTAINERS.md new file mode 100644 index 0000000..1aaefce --- /dev/null +++ b/examples/ske-gpu-operator/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (mauritz.uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/ske-gpu-operator/README.md b/examples/ske-gpu-operator/README.md new file mode 100644 index 0000000..8dedc84 --- /dev/null +++ b/examples/ske-gpu-operator/README.md @@ -0,0 +1,7 @@ +# SKE Kubernetes GPU Operator Installation + +## Overview + +This example demonstrates how to deploy a SKE cluster with an NVIDIA H100 node pool and install the GPU Operator. + +**Note:** Currently, GPU-enabled node pools on SKE are only supported when using Ubuntu as the node operating system. diff --git a/examples/ske-gpu-operator/gpu-operator-values.yaml.tftpl b/examples/ske-gpu-operator/gpu-operator-values.yaml.tftpl new file mode 100644 index 0000000..c8208aa --- /dev/null +++ b/examples/ske-gpu-operator/gpu-operator-values.yaml.tftpl @@ -0,0 +1,10 @@ +dcgm: + enabled: true + +dcgmExporter: + enabled: true + serviceMonitor: + enabled: true + additionalLabels: + # this label needs to be set for prometheus to use the service monitor + release: kube-prometheus-stack diff --git a/examples/ske-gpu-operator/main.tf b/examples/ske-gpu-operator/main.tf new file mode 100644 index 0000000..b0b8c98 --- /dev/null +++ b/examples/ske-gpu-operator/main.tf @@ -0,0 +1,157 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.60.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">=2.14.0" + } + } +} + +variable "project_id" { + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +variable "stackit_service_account_key_path" { + default = "" +} + +provider "kubernetes" { + host = yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.certificate-authority-data) +} + +provider "helm" { + kubernetes = { + host = yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.this.kube_config).clusters.0.cluster.certificate-authority-data) + } +} + +provider "stackit" { + default_region = "eu01" + service_account_key_path = var.stackit_service_account_key_path +} + +resource "stackit_ske_kubeconfig" "this" { + project_id = var.project_id + cluster_name = stackit_ske_cluster.this.name + refresh = true + + depends_on = [stackit_ske_cluster.this] +} + +data "stackit_ske_kubernetes_versions" "this" { + version_state = "SUPPORTED" +} + +data "stackit_ske_machine_image_versions" "this" { + version_state = "SUPPORTED" +} + +locals { + flatcar_supported_version = one(flatten([ + for mi in data.stackit_ske_machine_image_versions.this.machine_images : [ + for v in mi.versions : + v.version + if mi.name == "flatcar" + ] + ])) + ubuntu_supported_version = one(flatten([ + for mi in data.stackit_ske_machine_image_versions.this.machine_images : [ + for v in mi.versions : + v.version + if mi.name == "ubuntu" + ] + ])) + gpu_operator_helm_values = templatefile("${path.module}/gpu-operator-values.yaml.tftpl", {}) +} + +resource "stackit_ske_cluster" "this" { + project_id = var.project_id + name = "ske-gpu" + kubernetes_version_min = data.stackit_ske_kubernetes_versions.this.kubernetes_versions.0.version + + maintenance = { + enable_kubernetes_version_updates = true + enable_machine_image_version_updates = true + start = "01:00:00Z" + end = "02:00:00Z" + } + + node_pools = [ + { + name = "standard" + machine_type = "g2i.4" + minimum = "3" + maximum = "9" + max_surge = "3" + availability_zones = ["eu01-1", "eu01-2", "eu01-3"] + os_version_min = local.flatcar_supported_version + os_name = "flatcar" + volume_size = 150 + volume_type = "storage_premium_perf6" + }, + { + name = "gpu-pool-h100-2" + machine_type = "n3.14d.g1" + os_version_min = local.ubuntu_supported_version + os_name = "ubuntu" + minimum = "1" + maximum = "1" + max_surge = "1" + availability_zones = ["eu01-2"] + volume_size = 150 + volume_type = "storage_premium_perf6" + labels = { + "dedicated" = "gpu" + } + taints = [ + { + effect = "NoSchedule" + key = "nvidia.com/gpu" + value = "true" + }, + ] + }, + ] +} + +resource "kubernetes_namespace_v1" "gpu_operator" { + metadata { + name = "gpu-operator" + } +} + +resource "helm_release" "gpu_operator" { + name = "gpu-operator" + namespace = kubernetes_namespace_v1.gpu_operator.metadata[0].name + repository = "https://helm.ngc.nvidia.com/nvidia" + chart = "gpu-operator" + version = "25.3.1" + + values = [ + local.gpu_operator_helm_values + ] +} diff --git a/examples/ske-kubernetes-terraform-provider/.terraform.lock.hcl b/examples/ske-kubernetes-terraform-provider/.terraform.lock.hcl new file mode 100644 index 0000000..de5a520 --- /dev/null +++ b/examples/ske-kubernetes-terraform-provider/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "2.38.0" + constraints = "~> 2.24" + hashes = [ + "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", + "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", + "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", + "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", + "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", + "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", + "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", + "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", + "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", + "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", + "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.94.0" + constraints = "~> 0.35" + hashes = [ + "h1:ikFzd4yeJ1LR8ojP2PsZwiK2ZLhxBjRXkEg2HJrI07U=", + "zh:06c8da7d8a048216e825fa7d1e45949c1bda2a5f53f9bb0556b83b6610703fe6", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:19e82636cfd52a65105e0cf030bc8a0c815082818ef953b84f9b1e349a87318c", + "zh:24af9b7d2f1bb38f480b1aa8cf5e4ecf483bd4403642a9e8a5accbe1ae212feb", + "zh:3b10850e9242bcd00c519ff4140130e8443002fd60b6dff90983e7cb1973b2c3", + "zh:54837a0fa4ddbcf0b8407718f8823b831322deba3bd7ec8492e4578928f50633", + "zh:5cfd6a6b1ca73826a03f8746ef84a5c4059648bc49abf8056c8e0f9b87800a23", + "zh:6ab3bcfef6ff65b4ce76d333b4ad99e5f91991fcf5bddbe1958aadde6ee05eab", + "zh:81b96dc29b055f15e475d8bc32482617a582785949b3c02f44ef15d19951f69c", + "zh:85f478c2fcf10219263462d0f06b5cc41603b1edad813c336e100b3e0a55bfe8", + "zh:9adbb7655fddfe4d4081746d0d7e39c3e8fbf8aa3d8b7d3b5164f30c16a6bd93", + "zh:9c24b39e788283ead8a8ce1f013a47562ff0dc1ccb642a8e18644cbdcda0f1c4", + "zh:a425f28d6a5f6f024cab56c848c55025e84a09db946f1b00a2655d9567251cea", + "zh:f28aa62d2f06e08fe6d18ef9103a8164aa9278540779bebd61120f810c603c6b", + ] +} diff --git a/examples/ske-nginx-rate-limit/.terraform.lock.hcl b/examples/ske-nginx-rate-limit/.terraform.lock.hcl index 1adc572..1c27261 100644 --- a/examples/ske-nginx-rate-limit/.terraform.lock.hcl +++ b/examples/ske-nginx-rate-limit/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/helm" { version = "3.1.1" hashes = [ "h1:47CqNwkxctJtL/N/JuEj+8QMg8mRNI/NWeKO5/ydfZU=", + "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", @@ -25,6 +26,7 @@ provider "registry.terraform.io/hashicorp/kubernetes" { constraints = ">= 2.14.0" hashes = [ "h1:P0c8knzZnouTNFIRij8IS7+pqd0OKaFDYX0j4GRsiqo=", + "h1:vyHdH0p6bf9xp1NPePObAJkXTJb/I09FQQmmevTzZe0=", "zh:02d55b0b2238fd17ffa12d5464593864e80f402b90b31f6e1bd02249b9727281", "zh:20b93a51bfeed82682b3c12f09bac3031f5bdb4977c47c97a042e4df4fb2f9ba", "zh:6e14486ecfaee38c09ccf33d4fdaf791409f90795c1b66e026c226fad8bc03c7", @@ -43,6 +45,7 @@ provider "registry.terraform.io/hashicorp/kubernetes" { provider "registry.terraform.io/hashicorp/random" { version = "3.8.1" hashes = [ + "h1:Eexl06+6J+s75uD46+WnZtpJZYRVUMB0AiuPBifK6Jc=", "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", @@ -63,6 +66,7 @@ provider "registry.terraform.io/stackitcloud/stackit" { version = "0.90.0" constraints = ">= 0.66.0" hashes = [ + "h1:QgP6TOtucJ3A6fA51rdUvxhYGjl9RrWvXQZpjHTOuiU=", "h1:W29Kv6XUxYssF2Gy8KcmTx3EFstt6k8sKgPRIBbq+qs=", "zh:003af58a84884558bbb2fc40fcbefa6774ec20aa9e4b97cf3f950190a600afd2", "zh:026ee9cef4670cf33369f8654c6b9b1d8c0e116ceb0b353c882be222951ecdd4", diff --git a/examples/ske-nginx-rate-limit/01-variables.tf b/examples/ske-nginx-rate-limit/01-variables.tf index 360890d..f87f3ab 100644 --- a/examples/ske-nginx-rate-limit/01-variables.tf +++ b/examples/ske-nginx-rate-limit/01-variables.tf @@ -14,7 +14,7 @@ variable "stackit_project_id" { type = string - default = "d75e6aab-b616-4b42-ae3b-aaf161ad626d" + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } variable "stackit_region" { diff --git a/examples/ske-observability-alerting-kube-state-metrics/.terraform.lock.hcl b/examples/ske-observability-alerting-kube-state-metrics/.terraform.lock.hcl new file mode 100644 index 0000000..3e3b5bf --- /dev/null +++ b/examples/ske-observability-alerting-kube-state-metrics/.terraform.lock.hcl @@ -0,0 +1,64 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/helm" { + version = "3.1.1" + constraints = ">= 3.1.1" + hashes = [ + "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.1.0" + constraints = ">= 3.1.0" + hashes = [ + "h1:oodIAuFMikXNmEtil5MQgP4dfSctUBYQiGJfjbsF3NY=", + "zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0", + "zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20", + "zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c", + "zh:1678c982853ad461e65ccb5e79d585e13ed109dd47dab2a66d3a7a304faeef65", + "zh:1c050a5c15e330457a9c18caacf61a923c59d663e13f2962e4b32f04fef523a0", + "zh:2c55bcec83be58ec132c7cb0a1ac644758b800d794fdc636d53a0eada0358a3a", + "zh:a062bb0aa316c08d8460c66a5d68da71da40de5d3bc3b31abcf3a1a9a19650f1", + "zh:a26fdea0afaa9b247c73c0b42843ca51ba7db0ac2571f9d3d50dcabd20ca1b98", + "zh:c872c9385a78d502bf5823d61cd3bb0f9a0585030e025eb12585c83451beeaa1", + "zh:f180879af931182beee4c8c0d9dab62b81d86f17ddcbe3786ef4c7cec9163a4e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f70f5789264069e0eef06f9b5d5fde955ef7206f7d446d1ce51a4c37a3f3e02f", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.94.0" + constraints = ">= 0.94.0" + hashes = [ + "h1:ikFzd4yeJ1LR8ojP2PsZwiK2ZLhxBjRXkEg2HJrI07U=", + "zh:06c8da7d8a048216e825fa7d1e45949c1bda2a5f53f9bb0556b83b6610703fe6", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:19e82636cfd52a65105e0cf030bc8a0c815082818ef953b84f9b1e349a87318c", + "zh:24af9b7d2f1bb38f480b1aa8cf5e4ecf483bd4403642a9e8a5accbe1ae212feb", + "zh:3b10850e9242bcd00c519ff4140130e8443002fd60b6dff90983e7cb1973b2c3", + "zh:54837a0fa4ddbcf0b8407718f8823b831322deba3bd7ec8492e4578928f50633", + "zh:5cfd6a6b1ca73826a03f8746ef84a5c4059648bc49abf8056c8e0f9b87800a23", + "zh:6ab3bcfef6ff65b4ce76d333b4ad99e5f91991fcf5bddbe1958aadde6ee05eab", + "zh:81b96dc29b055f15e475d8bc32482617a582785949b3c02f44ef15d19951f69c", + "zh:85f478c2fcf10219263462d0f06b5cc41603b1edad813c336e100b3e0a55bfe8", + "zh:9adbb7655fddfe4d4081746d0d7e39c3e8fbf8aa3d8b7d3b5164f30c16a6bd93", + "zh:9c24b39e788283ead8a8ce1f013a47562ff0dc1ccb642a8e18644cbdcda0f1c4", + "zh:a425f28d6a5f6f024cab56c848c55025e84a09db946f1b00a2655d9567251cea", + "zh:f28aa62d2f06e08fe6d18ef9103a8164aa9278540779bebd61120f810c603c6b", + ] +} diff --git a/examples/ske-observability-alerting-kube-state-metrics/main.tf b/examples/ske-observability-alerting-kube-state-metrics/main.tf index 17520b1..0194bc1 100644 --- a/examples/ske-observability-alerting-kube-state-metrics/main.tf +++ b/examples/ske-observability-alerting-kube-state-metrics/main.tf @@ -12,27 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -provider "stackit" { - default_region = "eu01" - service_account_key_path = "" -} - -provider "kubernetes" { - host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server - client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) - client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) - cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) -} - -provider "helm" { - kubernetes { - host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server - client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) - client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) - cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) - } -} - resource "stackit_ske_cluster" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example" diff --git a/examples/ske-observability-alerting-kube-state-metrics/provider.tf b/examples/ske-observability-alerting-kube-state-metrics/provider.tf new file mode 100644 index 0000000..430bb85 --- /dev/null +++ b/examples/ske-observability-alerting-kube-state-metrics/provider.tf @@ -0,0 +1,51 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.94.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 3.1.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 3.1.1" + } + } +} + +provider "stackit" { + default_region = "eu01" + service_account_key_path = "" +} + +provider "kubernetes" { + host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) +} + +provider "helm" { + kubernetes = { + host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) + } +} diff --git a/examples/ske-observability-log-alerts/.terraform.lock.hcl b/examples/ske-observability-log-alerts/.terraform.lock.hcl new file mode 100644 index 0000000..3e3b5bf --- /dev/null +++ b/examples/ske-observability-log-alerts/.terraform.lock.hcl @@ -0,0 +1,64 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/helm" { + version = "3.1.1" + constraints = ">= 3.1.1" + hashes = [ + "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.1.0" + constraints = ">= 3.1.0" + hashes = [ + "h1:oodIAuFMikXNmEtil5MQgP4dfSctUBYQiGJfjbsF3NY=", + "zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0", + "zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20", + "zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c", + "zh:1678c982853ad461e65ccb5e79d585e13ed109dd47dab2a66d3a7a304faeef65", + "zh:1c050a5c15e330457a9c18caacf61a923c59d663e13f2962e4b32f04fef523a0", + "zh:2c55bcec83be58ec132c7cb0a1ac644758b800d794fdc636d53a0eada0358a3a", + "zh:a062bb0aa316c08d8460c66a5d68da71da40de5d3bc3b31abcf3a1a9a19650f1", + "zh:a26fdea0afaa9b247c73c0b42843ca51ba7db0ac2571f9d3d50dcabd20ca1b98", + "zh:c872c9385a78d502bf5823d61cd3bb0f9a0585030e025eb12585c83451beeaa1", + "zh:f180879af931182beee4c8c0d9dab62b81d86f17ddcbe3786ef4c7cec9163a4e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f70f5789264069e0eef06f9b5d5fde955ef7206f7d446d1ce51a4c37a3f3e02f", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.94.0" + constraints = ">= 0.94.0" + hashes = [ + "h1:ikFzd4yeJ1LR8ojP2PsZwiK2ZLhxBjRXkEg2HJrI07U=", + "zh:06c8da7d8a048216e825fa7d1e45949c1bda2a5f53f9bb0556b83b6610703fe6", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:19e82636cfd52a65105e0cf030bc8a0c815082818ef953b84f9b1e349a87318c", + "zh:24af9b7d2f1bb38f480b1aa8cf5e4ecf483bd4403642a9e8a5accbe1ae212feb", + "zh:3b10850e9242bcd00c519ff4140130e8443002fd60b6dff90983e7cb1973b2c3", + "zh:54837a0fa4ddbcf0b8407718f8823b831322deba3bd7ec8492e4578928f50633", + "zh:5cfd6a6b1ca73826a03f8746ef84a5c4059648bc49abf8056c8e0f9b87800a23", + "zh:6ab3bcfef6ff65b4ce76d333b4ad99e5f91991fcf5bddbe1958aadde6ee05eab", + "zh:81b96dc29b055f15e475d8bc32482617a582785949b3c02f44ef15d19951f69c", + "zh:85f478c2fcf10219263462d0f06b5cc41603b1edad813c336e100b3e0a55bfe8", + "zh:9adbb7655fddfe4d4081746d0d7e39c3e8fbf8aa3d8b7d3b5164f30c16a6bd93", + "zh:9c24b39e788283ead8a8ce1f013a47562ff0dc1ccb642a8e18644cbdcda0f1c4", + "zh:a425f28d6a5f6f024cab56c848c55025e84a09db946f1b00a2655d9567251cea", + "zh:f28aa62d2f06e08fe6d18ef9103a8164aa9278540779bebd61120f810c603c6b", + ] +} diff --git a/examples/ske-observability-log-alerts/main.tf b/examples/ske-observability-log-alerts/main.tf index 21b3e6a..1be4fc0 100644 --- a/examples/ske-observability-log-alerts/main.tf +++ b/examples/ske-observability-log-alerts/main.tf @@ -12,27 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -provider "stackit" { - default_region = "eu01" - service_account_key_path = "" -} - -provider "kubernetes" { - host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server - client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) - client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) - cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) -} - -provider "helm" { - kubernetes { - host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server - client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) - client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) - cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) - } -} - resource "stackit_ske_cluster" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example" diff --git a/examples/ske-observability-log-alerts/provider.tf b/examples/ske-observability-log-alerts/provider.tf new file mode 100644 index 0000000..430bb85 --- /dev/null +++ b/examples/ske-observability-log-alerts/provider.tf @@ -0,0 +1,51 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.94.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 3.1.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 3.1.1" + } + } +} + +provider "stackit" { + default_region = "eu01" + service_account_key_path = "" +} + +provider "kubernetes" { + host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) +} + +provider "helm" { + kubernetes = { + host = yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.server + client_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-certificate-data) + client_key = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).users.0.user.client-key-data) + cluster_ca_certificate = base64decode(yamldecode(stackit_ske_kubeconfig.example.kube_config).clusters.0.cluster.certificate-authority-data) + } +} diff --git a/examples/ske-stackit-sfs-integration/.terraform.lock.hcl b/examples/ske-stackit-sfs-integration/.terraform.lock.hcl index 75c9e75..d3f19c1 100644 --- a/examples/ske-stackit-sfs-integration/.terraform.lock.hcl +++ b/examples/ske-stackit-sfs-integration/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/stackitcloud/stackit" { version = "0.79.0" constraints = "0.79.0" hashes = [ + "h1:AB51ok4llxeTmkVadjYpsafPbzSU5xEHLzcVBuVHxqc=", "h1:l7AeT3WWi/u7QB7E1SaksYc5VjU9JS2LYc4OnavI3kw=", "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", "zh:1eb8276c0d8a4b5b92534020df0cb270ed7c4d91dfed6db089ee775b50a8f5e3", diff --git a/examples/ske-stackit-sfs-integration/01-config.tf b/examples/ske-stackit-sfs-integration/01-config.tf index 10a65ba..8ecf802 100644 --- a/examples/ske-stackit-sfs-integration/01-config.tf +++ b/examples/ske-stackit-sfs-integration/01-config.tf @@ -41,11 +41,11 @@ variable "LOCAL_SUBNET" { variable "STACKIT_PROJECT_ID" { type = string description = "STACKIT Project ID" - default = "16ec118f-90d0-466d-8393-99eea504c536" + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } variable "STACKIT_ORG_ID" { type = string description = "STACKIT Org ID" - default = "03a34540-3c1a-4794-b2c6-7111ecf824ef" + default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } diff --git a/examples/ske-stackit-sfs-integration/04-project.tf b/examples/ske-stackit-sfs-integration/04-project.tf index 4fd3604..69088af 100644 --- a/examples/ske-stackit-sfs-integration/04-project.tf +++ b/examples/ske-stackit-sfs-integration/04-project.tf @@ -22,7 +22,7 @@ resource "stackit_resourcemanager_project" "sfs-no-folder" { } resource "stackit_resourcemanager_project" "sfs-folder" { - parent_container_id = "bc229fa8-4be4-42d5-8808-514fe6d39074" #Folder ID Demos + parent_container_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #Folder ID Demos name = "sfs-example-folder" labels = { "networkArea" = stackit_network_area.sfs.network_area_id diff --git a/examples/stackit-landing-zone/README.md b/examples/stackit-landing-zone/README.md new file mode 100644 index 0000000..5173e22 --- /dev/null +++ b/examples/stackit-landing-zone/README.md @@ -0,0 +1,4 @@ +# STACKIT Landing Zone + +For the full Terraform Landing Zone implementation, please visit: +[STACKIT Terraform Landing Zone Repository](https://github.com/stackitcloud/stackit-landing-zone) diff --git a/examples/vpn-usecases/README.md b/examples/vpn-usecases/README.md new file mode 100644 index 0000000..27e0755 --- /dev/null +++ b/examples/vpn-usecases/README.md @@ -0,0 +1,7 @@ +# VPN Usecases + +> ⚠️ Note: Currently, we are still leveraging the restful provider to roll out managed VPN resources. Native integration for the STACKIT Terraform provider is a work in progress. We will update these examples once it is released. + +- [`STACKIT-STACKIT`](stackit-stackit) +- [`STACKIT-GCP`](stackit-gcp) +- [`STACKIT-AZURE`](stackit-azure) diff --git a/examples/vpn-usecases/module/stackit-sna-with-debug-machine/README.md b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/README.md new file mode 100644 index 0000000..509f3de --- /dev/null +++ b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/README.md @@ -0,0 +1,70 @@ +# SNA with test-machine module + +This module is used to quickly spin up a sna with a test virtual machine. We use this module to debug vpn connectivity. + +> ⚠️ **SECURITY WARNING** +> Be careful: By default, **port security is disabled** on the network interface to allow unrestricted traffic for debugging purposes. **Do not use this module in a production environment**. + + + +## Requirements + +| Name | Version | +| ------------------------------------------------------------------ | -------- | +| [stackit](#requirement_stackit) | >=0.95.0 | + +## Providers + +| Name | Version | +| ------------------------------------------------------------ | -------- | +| [stackit](#provider_stackit) | >=0.95.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| -------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| [stackit_network.this](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.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_resourcemanager_project.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/resourcemanager_project) | resource | +| [stackit_server.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/server) | resource | +| [stackit_volume.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/volume) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------- | :------: | +| [machine_availability_zone](#input_machine_availability_zone) | The availability zone (e.g. eu01-1) | `string` | n/a | yes | +| [machine_disk_performance_class](#input_machine_disk_performance_class) | Storage performance class | `string` | `"storage_premium_perf4"` | no | +| [machine_disk_size](#input_machine_disk_size) | Boot volume size in GB | `number` | `20` | no | +| [machine_image_id](#input_machine_image_id) | Image UUID (Default: Debian 12) | `string` | `"c751cde7-e648-4f81-9722-ce9c7848bed0"` | no | +| [machine_ipv4_prefix](#input_machine_ipv4_prefix) | The IPv4 prefix for the test machine's network (CIDR notation). This must be a subnet within the defined SNA network ranges. | `string` | n/a | yes | +| [machine_name](#input_machine_name) | name of the stackit test machine | `string` | n/a | yes | +| [machine_network_name](#input_machine_network_name) | The name of the network where the test machine will be connected. | `string` | n/a | yes | +| [machine_type](#input_machine_type) | Flavor of the machine | `string` | `"c2i.1"` | no | +| [sna_default_nameserver](#input_sna_default_nameserver) | A list of STACKIT SNA default nameservers (IP addresses). | `list(string)` |
[
"1.1.1.1"
]
| no | +| [sna_name](#input_sna_name) | The name of the STACKIT Network Area (SNA). | `string` | n/a | yes | +| [sna_network_range_prefix](#input_sna_network_range_prefix) | A list of STACKIT SNA network range prefixes in CIDR notation. | `list(string)` |
[
"10.28.0.0/16"
]
| no | +| [sna_transfer_range](#input_sna_transfer_range) | The STACKIT SNA transfer range in CIDR notation. | `string` | `"172.16.0.0/16"` | no | +| [stackit_admin_email](#input_stackit_admin_email) | The email address of the project administrator. | `string` | n/a | yes | +| [stackit_org_id](#input_stackit_org_id) | The STACKIT Organization ID (UUID). | `string` | n/a | yes | +| [stackit_project_name](#input_stackit_project_name) | The name of the STACKIT project where the managed VPN and test machine will be deployed. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| [machine_network_ipv4](#output_machine_network_ipv4) | The IPv4 prefix of the machine's network. | +| [machine_private_ipv4](#output_machine_private_ipv4) | The private IP address of the test machine. | +| [machine_public_ip](#output_machine_public_ip) | The public IP address of the test machine. | +| [project_id](#output_project_id) | The ID of the STACKIT project. | +| [sna_id](#output_sna_id) | The ID of the STACKIT Network Area. | +| [sna_network_range](#output_sna_network_range) | The network ranges (sna-ipv4) of the STACKIT Network Area. | + + diff --git a/examples/vpn-usecases/module/stackit-sna-with-debug-machine/debug-user.yml b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/debug-user.yml new file mode 100644 index 0000000..865ffb5 --- /dev/null +++ b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/debug-user.yml @@ -0,0 +1,25 @@ +#cloud-config +# --------------------------------------------------------------------------- +# Example cloud-init for Linux instances (RHEL / Debian). +# Creates a local admin user for initial access. +# +# IMPORTANT: Replace the password hash below with a hash of your own +# secure password before deploying. Never use a shared or well-known +# default password in production. +# +# Generate a SHA-512 hash on Linux/macOS: +# openssl passwd -6 "YourPassword" +# --------------------------------------------------------------------------- +users: + - name: debug + groups: sudo + shell: /bin/bash + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + lock_passwd: false + # debug123 + passwd: "$6$dIIA5b1oK7qi89.P$MH9SSMtnzCo8QvvUnVoCE5e1c7FY0NUgB4dpsv5JDq9zpRDiTpfiYtM5DitiJIuQWvZ7T1emTTgKaBufayaIW." + +chpasswd: + expire: false + +ssh_pwauth: true diff --git a/examples/vpn-usecases/module/stackit-sna-with-debug-machine/main.tf b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/main.tf new file mode 100644 index 0000000..a8f09c4 --- /dev/null +++ b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/main.tf @@ -0,0 +1,96 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_network_area" "this" { + name = var.sna_name + organization_id = var.stackit_org_id + labels = { + "preview/routingtables" = "true" + } +} + +resource "stackit_network_area_region" "this" { + organization_id = var.stackit_org_id + network_area_id = stackit_network_area.this.network_area_id + ipv4 = { + transfer_network = var.sna_transfer_range + network_ranges = [ + for prefix in var.sna_network_range_prefix : { + prefix = prefix + } + ] + default_nameservers = var.sna_default_nameserver + } +} + +resource "stackit_resourcemanager_project" "this" { + parent_container_id = var.stackit_org_id + name = var.stackit_project_name + owner_email = var.stackit_admin_email + labels = { + "networkArea" = stackit_network_area.this.network_area_id + } +} + +resource "stackit_volume" "this" { + project_id = stackit_resourcemanager_project.this.project_id + name = "${var.machine_name}-volume" + availability_zone = var.machine_availability_zone + size = var.machine_disk_size + performance_class = var.machine_disk_performance_class + source = { + type = "image" + id = var.machine_image_id + } +} + +resource "stackit_network" "this" { + name = var.machine_network_name + project_id = stackit_resourcemanager_project.this.project_id + ipv4_prefix = var.machine_ipv4_prefix + ipv4_nameservers = var.sna_default_nameserver +} + +resource "stackit_network_interface" "this" { + project_id = stackit_resourcemanager_project.this.project_id + network_id = stackit_network.this.network_id + security = false +} + +resource "stackit_server" "this" { + project_id = stackit_resourcemanager_project.this.project_id + name = var.machine_name + availability_zone = var.machine_availability_zone + machine_type = var.machine_type + + boot_volume = { + source_type = "volume" + source_id = stackit_volume.this.volume_id + } + + agent = { + provisioning_policy = "ALWAYS" + } + + network_interfaces = [ + stackit_network_interface.this.network_interface_id + ] + + user_data = file("${path.module}/debug-user.yml") +} + +resource "stackit_public_ip" "this" { + project_id = stackit_resourcemanager_project.this.project_id + network_interface_id = stackit_network_interface.this.network_interface_id +} diff --git a/examples/vpn-usecases/module/stackit-sna-with-debug-machine/outputs.tf b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/outputs.tf new file mode 100644 index 0000000..ec2f79d --- /dev/null +++ b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/outputs.tf @@ -0,0 +1,43 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +output "sna_id" { + description = "The ID of the STACKIT Network Area." + value = stackit_network_area.this.network_area_id +} + +output "project_id" { + description = "The ID of the STACKIT project." + value = stackit_resourcemanager_project.this.project_id +} + +output "machine_public_ip" { + description = "The public IP address of the test machine." + value = stackit_public_ip.this.ip +} + +output "machine_private_ipv4" { + description = "The private IP address of the test machine." + value = stackit_network_interface.this.ipv4 +} + +output "machine_network_ipv4" { + description = "The IPv4 prefix of the machine's network." + value = stackit_network.this.ipv4_prefix +} + +output "sna_network_range" { + description = "The network ranges (sna-ipv4) of the STACKIT Network Area." + value = stackit_network_area_region.this.ipv4.network_ranges +} diff --git a/examples/vpn-usecases/module/stackit-sna-with-debug-machine/provider.tf b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/provider.tf new file mode 100644 index 0000000..edbbfce --- /dev/null +++ b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/provider.tf @@ -0,0 +1,22 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.95.0" + } + } +} diff --git a/examples/vpn-usecases/module/stackit-sna-with-debug-machine/variables.tf b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/variables.tf new file mode 100644 index 0000000..a3fce06 --- /dev/null +++ b/examples/vpn-usecases/module/stackit-sna-with-debug-machine/variables.tf @@ -0,0 +1,138 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_org_id" { + description = "The STACKIT Organization ID (UUID)." + type = string + validation { + condition = can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.stackit_org_id)) + error_message = "The stackit_org_id must be a valid UUID." + } +} + +variable "stackit_project_name" { + description = "The name of the STACKIT project where the managed VPN and test machine will be deployed." + type = string + validation { + condition = length(var.stackit_project_name) >= 1 && length(var.stackit_project_name) <= 63 + error_message = "The project name must be between 1 and 63 characters long." + } +} + +variable "stackit_admin_email" { + description = "The email address of the project administrator." + type = string + validation { + condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.stackit_admin_email)) + error_message = "The stackit_admin_email must be a valid email address." + } +} + +variable "sna_name" { + description = "The name of the STACKIT Network Area (SNA)." + type = string +} + +variable "sna_transfer_range" { + description = "The STACKIT SNA transfer range in CIDR notation." + type = string + default = "172.16.0.0/16" + validation { + condition = can(cidrnetmask(var.sna_transfer_range)) + error_message = "The sna_transfer_range must be a valid CIDR notation." + } +} + +variable "sna_network_range_prefix" { + description = "A list of STACKIT SNA network range prefixes in CIDR notation." + type = list(string) + default = ["10.28.0.0/16"] + validation { + condition = alltrue([for r in var.sna_network_range_prefix : can(cidrnetmask(r))]) + error_message = "All elements in sna_network_range_prefix must be valid CIDR notations." + } +} + +variable "sna_default_nameserver" { + description = "A list of STACKIT SNA default nameservers (IP addresses)." + type = list(string) + default = ["1.1.1.1"] + validation { + condition = alltrue([for ns in var.sna_default_nameserver : can(regex("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$", ns))]) + error_message = "All elements in sna_default_nameserver must be valid IP addresses." + } +} + +variable "machine_network_name" { + description = "The name of the network where the test machine will be connected." + type = string +} + +variable "machine_ipv4_prefix" { + description = "The IPv4 prefix for the test machine's network (CIDR notation). This must be a subnet within the defined SNA network ranges." + type = string + validation { + condition = can(cidrnetmask(var.machine_ipv4_prefix)) + error_message = "The machine_ipv4_prefix must be a valid CIDR notation." + } +} + +variable "machine_name" { + type = string + description = "name of the stackit test machine" +} + +variable "machine_availability_zone" { + description = "The availability zone (e.g. eu01-1)" + type = string + + validation { + condition = can(regex("^[a-z]{2}[0-9]{2}-[a-zA-Z0-9]+$", var.machine_availability_zone)) + error_message = "The availability zone must follow the STACKIT pattern (e.g., eu01-1, eu01-m)." + } +} + +variable "machine_type" { + description = "Flavor of the machine" + type = string + default = "c2i.1" +} + +variable "machine_image_id" { + description = "Image UUID (Default: Debian 12)" + type = string + default = "c751cde7-e648-4f81-9722-ce9c7848bed0" + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$", var.machine_image_id)) + error_message = "The image_id must be a valid UUID." + } +} + +variable "machine_disk_size" { + description = "Boot volume size in GB" + type = number + default = 20 + + validation { + condition = var.machine_disk_size >= 1 + error_message = "The disk_size must be at least 1 GB." + } +} + +variable "machine_disk_performance_class" { + description = "Storage performance class" + type = string + default = "storage_premium_perf4" +} diff --git a/examples/vpn-usecases/stackit-azure/.terraform.lock.hcl b/examples/vpn-usecases/stackit-azure/.terraform.lock.hcl new file mode 100644 index 0000000..e793dcf --- /dev/null +++ b/examples/vpn-usecases/stackit-azure/.terraform.lock.hcl @@ -0,0 +1,105 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "4.72.0" + constraints = "4.72.0" + hashes = [ + "h1:QYnPAHT/PYheOOZz52ucHqw/ZO9PxWyPLtO7UD/jSMg=", + "zh:073472587c3752e89738522814d2b4eb2fd69eb2cb19c5a5ead3c7d2eabdc279", + "zh:1950effc0c315b6002c8cb6327b94fe59bda210e699367d9727bc66490d651d2", + "zh:47c990db75658525de57c8955a05b4752b88f3a900fffac0e7661d4a749e94f2", + "zh:610f2cbd6fab76750d8b093f03beabbb7162dc8c6affe0109f534ce240b3ff0f", + "zh:6739d645fe548c5a489d711f7748f32368cf68d723d2c59d3f2e21456304d692", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a277ab095cc8aff3aede9e43eca2a699936472ef90abb272adf3daa609eb9141", + "zh:b1fdcdaf926c86de0d884beda90d78cb94a42ddede03a1f0b92c36b321d4f07e", + "zh:c003f1f15e52c54e189301ae2c7d8dd65acb2e5a7527d201355f2757b5465ba9", + "zh:c45f2d2206c0f8f71f207cd39eec73da9619d35932bbe1a5b8be7679c50a151e", + "zh:d7040d8ec295481bc1d30346ed7f3075c40ede87c0fedf1db34dd91c1c367a10", + "zh:e595f0b870cd5fd5debdc926fc1740201d2b66188b9b132dc598bdd6444e7348", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.9.0" + hashes = [ + "h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=", + "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", + "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", + "zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f", + "zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0", + "zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61", + "zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc", + "zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e", + "zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef", + "zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b", + "zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257", + "zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.14.0" + hashes = [ + "h1:/hlxsUpuN/lvPTNL9+NyVGsOyRsK5NsxwFMsj5CdOp4=", + "zh:12abfd6b800e4d7fa6db7310dec8ffd440b31993861ef188c7ed5260b3073937", + "zh:23005521e800bb19e1597bf755c5f70d675d30b685d4255001ed5fa47d9df3f1", + "zh:2fea249b582ae97cd1cc10385187ea50993bb47c28cc5df0305e57ceaabf0a10", + "zh:322018d3b987b7aad08697178029a2bb667bed699e88328f0c89c52a2fd41341", + "zh:32a08e98fce2d273cb9b2c89d6c54727cc9f0a32e15bfd896be4e02cc6b48f95", + "zh:3db89aabd0e619616bd4b0f8b373a7586dfe60feffcea12a84a0bdbc445714b3", + "zh:7488f56c81d742dc020f29063626c8f07ca188aa97be61e7307e8d62397020a2", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7cb4067f2e7559b13f7562ef722f948950901eb37834873e98360ab28f66e9d7", + "zh:9d552c8345f61e1b7db8e725144981345f18ac1014d58d6f5ddf0928a195fffb", + "zh:a8e69fb6b97fc9d86fb19a9f4d42abe33c4a68e700b15387ce2e17d2b9934bed", + "zh:aeeb900eb8dd0f790c60ea5c0e0c8d42bd6e4a54f391681d4decca15b544394b", + "zh:c239c619101a8c95e1f14061eb973c57a8d15fa0e68878ced5bbd76858ee5b79", + ] +} + +provider "registry.terraform.io/magodo/restful" { + version = "0.25.2" + hashes = [ + "h1:gvoDTFfxp7n1B4Wsnx9IC7Ku8g8tdVR4mCC6TDX0Mws=", + "zh:0513ff62fce41a59462f39e1c4636f3c87e6f8d24ee579075900d3e0f57f6992", + "zh:1a3e39e6b8c7fd0f3983730944a029db8f00557922e337cff0567a07c5e74b45", + "zh:2527c96fcc45458efc9eca1c66cee98269d80693b571c57baee783402bfbaa28", + "zh:50cec9afe8b55629d1c94d477b26ff95de8cc8e3304f6c2bfc5dad3bccc6decc", + "zh:89e94c0f312d0ef4213b46ee776a27f6a5d114520c08a4716f4fee4c26c16f91", + "zh:9a9762ebaf9567a4aa34a1911f051527696241679e087137fcc7821e52b66483", + "zh:a065be3488e24928199904f4a496974c03fdcf2b06fccf016e405b3068d5ef76", + "zh:c62a1a6fb3c5135451f68ea4ed1f66d999ab654323d10526756e83f6f77d6bdf", + "zh:cf01364f89b713dc10eb87098839317e6f2de222bec2597923cddbb07bdd9c13", + "zh:dc0ac6a1e5e3199e1d35fb49f9de1d9325caa3c0d3e87ea8128295e19ac941c3", + "zh:e55cf6e8230f081b7c8ade592c14f1b8b45ee0aaa14c2bde2da9531d819a4392", + "zh:f333748916e68050c8935d760d6b9b469dd76eb94363af93562cbd076dba6ff5", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", + "zh:fdfdca8b7976c1a8b1b6a3589b4bfec277beb6dfb40c5568271d42f0b2f88a9d", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.95.0" + constraints = ">= 0.87.0, >= 0.95.0" + hashes = [ + "h1:sKmc6SGKEFglXKLMtOluJkFm7tzQZKQV3/QxUbHug1E=", + "zh:023edbb8ca984233bb51605a9005d4f7cb3365f0b11ddd68d911a1e30ccf64be", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:1fc43ed3055c4912e5b3ae2eba49dd5407beaa9ba6617612f317543f7d26ccd7", + "zh:31e587a9f279661b74b139e2a964a7d1c54a4073d27d21c2f948e0e7ba4c0d04", + "zh:37427a23800dff84c1b89d4985cb935c0112c59acd716d8920c160221c459061", + "zh:3a575f5c7d1252d99aea9187923087e1d483b2b34e42c9f058f557ec28c45d84", + "zh:44c7ee340e1a09d6f9a9873959f8283f0d73aee4a8c884f7b36985f943874b65", + "zh:4fab8fe953a0d4c21589cd36d23afe072c6403e37620c82839f9f829139cbbbf", + "zh:69fc061b3c7ea82d9e9a31d3665a535f6bb9dc3d6ff5b466f940d3d04a105e19", + "zh:85ee00442eff70ea103a96276efb5e1485b661b0a1db08bcdbd28b11b1f966e6", + "zh:9761ef1321c93cb3e4bc2499995b3e7ae910e2ae68b3228164dea73e9687ddb5", + "zh:b158a4e4726a4d4c9f61c5dc71abb4ca3a621269e1d7af88ab36d34c3bcec66f", + "zh:da37df9a426d83da8f6c1340104a6b9a7eef4a7e2589d0b70a89c574a1b3cc78", + "zh:e6421b9a351b2c9b2ab2341f2c07d863eb2ed055b847ea839de96b0fd62baf97", + ] +} diff --git a/examples/vpn-usecases/stackit-azure/010-provider.tf b/examples/vpn-usecases/stackit-azure/010-provider.tf new file mode 100644 index 0000000..88f7674 --- /dev/null +++ b/examples/vpn-usecases/stackit-azure/010-provider.tf @@ -0,0 +1,54 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.95.0" + } + restful = { + source = "magodo/restful" + } + azurerm = { + source = "hashicorp/azurerm" + version = "4.72.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + enable_beta_resources = true +} + +provider "azurerm" { + features {} + subscription_id = var.azure_subscription_id +} + +ephemeral "stackit_access_token" "this" {} + +provider "restful" { + alias = "stackit" + base_url = "https://vpn.api.eu01.stackit.cloud" + security = { + http = { + token = { + token = ephemeral.stackit_access_token.this.access_token + } + } + } +} diff --git a/examples/vpn-usecases/stackit-azure/020-variables.tf b/examples/vpn-usecases/stackit-azure/020-variables.tf new file mode 100644 index 0000000..ae3c32e --- /dev/null +++ b/examples/vpn-usecases/stackit-azure/020-variables.tf @@ -0,0 +1,34 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_org_id" { + type = string +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string +} + +variable "azure_subscription_id" { + type = string +} + +variable "stackit_admin_email" { + type = string +} diff --git a/examples/vpn-usecases/stackit-azure/030-stackit-azure-vpn.tf b/examples/vpn-usecases/stackit-azure/030-stackit-azure-vpn.tf new file mode 100644 index 0000000..2d3d7a2 --- /dev/null +++ b/examples/vpn-usecases/stackit-azure/030-stackit-azure-vpn.tf @@ -0,0 +1,376 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# STACKIT Side (vpn-sna-01) +module "vpn_sna_01" { + source = "../module/stackit-sna-with-debug-machine" + machine_availability_zone = "eu01-1" + machine_ipv4_prefix = "10.10.10.0/24" + machine_network_name = "vpn-sna-01" + sna_name = "vpn-sna-01" + machine_name = "vpn-sna-01" + stackit_admin_email = var.stackit_admin_email + stackit_org_id = var.stackit_org_id + stackit_project_name = "vpn-sna-01" + sna_network_range_prefix = [ + "10.10.0.0/16" + ] +} + +resource "restful_resource" "vpn_01_gateway" { + provider = restful.stackit + path = "/v1/projects/${module.vpn_sna_01.project_id}/regions/eu01/gateways" + body = { + availabilityZones = { + tunnel1 = "eu01-1" + tunnel2 = "eu01-2" + } + bgp = { + localAsn = 64512 + overrideAdvertisedRoutes = ["10.10.0.0/16"] + } + displayName = "vpn01" + labels = null + planId = "p500" + routingType = "BGP_ROUTE_BASED" + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" +} + +data "restful_resource" "vpn_01_gateway_status" { + provider = restful.stackit + id = "${restful_resource.vpn_01_gateway.id}/status" +} + +resource "random_password" "vpn_psk" { + length = 32 + special = false +} + +# Azure Side +resource "azurerm_resource_group" "rg" { + name = "rg-vpn-test" + location = "West Europe" +} + +# 1. Azure VNet and Subnets +resource "azurerm_virtual_network" "azure_vnet" { + name = "azure-vpn-network" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + address_space = ["10.11.0.0/16"] +} + +resource "azurerm_subnet" "azure_gateway_subnet" { + name = "GatewaySubnet" # MUST be exactly named GatewaySubnet + resource_group_name = azurerm_resource_group.rg.name + virtual_network_name = azurerm_virtual_network.azure_vnet.name + address_prefixes = ["10.11.0.0/24"] +} + +resource "azurerm_subnet" "azure_vm_subnet" { + name = "vm-subnet" + resource_group_name = azurerm_resource_group.rg.name + virtual_network_name = azurerm_virtual_network.azure_vnet.name + address_prefixes = ["10.11.1.0/24"] +} + +# 2. Azure Public IPs (2 required for Active-Active HA VPN) +resource "azurerm_public_ip" "azure_gw_pip1" { + name = "azure-gw-pip1" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + allocation_method = "Static" + sku = "Standard" + zones = ["1", "2", "3"] + + lifecycle { + ignore_changes = [domain_name_label] + } +} + +resource "azurerm_public_ip" "azure_gw_pip2" { + name = "azure-gw-pip2" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + allocation_method = "Static" + sku = "Standard" + zones = ["1", "2", "3"] + + lifecycle { + ignore_changes = [domain_name_label] + } +} + +# 3. Azure HA VPN Gateway +resource "azurerm_virtual_network_gateway" "azure_gateway" { + name = "azure-ha-vpn" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + + type = "Vpn" + vpn_type = "RouteBased" + active_active = true + bgp_enabled = true + sku = "VpnGw1AZ" + + bgp_settings { + asn = 64513 # Azure's local ASN + peering_addresses { + ip_configuration_name = "vnetGatewayConfig1" + apipa_addresses = ["169.254.21.2"] + } + peering_addresses { + ip_configuration_name = "vnetGatewayConfig2" + apipa_addresses = ["169.254.21.6"] + } + } + + ip_configuration { + name = "vnetGatewayConfig1" + public_ip_address_id = azurerm_public_ip.azure_gw_pip1.id + private_ip_address_allocation = "Dynamic" + subnet_id = azurerm_subnet.azure_gateway_subnet.id + } + + ip_configuration { + name = "vnetGatewayConfig2" + public_ip_address_id = azurerm_public_ip.azure_gw_pip2.id + private_ip_address_allocation = "Dynamic" + subnet_id = azurerm_subnet.azure_gateway_subnet.id + } +} + +# 4. Azure Local Network Gateways (Represents the 2 STACKIT Tunnels) +resource "azurerm_local_network_gateway" "stackit_tunnel1" { + name = "stackit-tunnel-1" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + gateway_address = data.restful_resource.vpn_01_gateway_status.output.tunnels[0].publicIP + + bgp_settings { + asn = 64512 + bgp_peering_address = "169.254.21.1" + } +} + +resource "azurerm_local_network_gateway" "stackit_tunnel2" { + name = "stackit-tunnel-2" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + gateway_address = data.restful_resource.vpn_01_gateway_status.output.tunnels[1].publicIP + + bgp_settings { + asn = 64512 + bgp_peering_address = "169.254.21.5" + } +} + +# 5. Azure VPN Connections +resource "azurerm_virtual_network_gateway_connection" "azure_tunnel1" { + name = "conn-to-stackit-tunnel1" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + + type = "IPsec" + virtual_network_gateway_id = azurerm_virtual_network_gateway.azure_gateway.id + local_network_gateway_id = azurerm_local_network_gateway.stackit_tunnel1.id + shared_key = random_password.vpn_psk.result + enable_bgp = true + + ipsec_policy { + ike_encryption = "GCMAES256" + ike_integrity = "SHA256" + ipsec_encryption = "GCMAES256" + ipsec_integrity = "GCMAES256" + dh_group = "DHGroup14" + pfs_group = "PFS14" + sa_lifetime = 3600 + sa_datasize = 102400000 + } +} + +resource "azurerm_virtual_network_gateway_connection" "azure_tunnel2" { + name = "conn-to-stackit-tunnel2" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + + type = "IPsec" + virtual_network_gateway_id = azurerm_virtual_network_gateway.azure_gateway.id + local_network_gateway_id = azurerm_local_network_gateway.stackit_tunnel2.id + shared_key = random_password.vpn_psk.result + enable_bgp = true + + ipsec_policy { + ike_encryption = "GCMAES256" + ike_integrity = "SHA256" + ipsec_encryption = "GCMAES256" + ipsec_integrity = "GCMAES256" + dh_group = "DHGroup14" + pfs_group = "PFS14" + sa_lifetime = 3600 + sa_datasize = 102400000 + } +} + +# Connection from STACKIT to Azure +resource "restful_resource" "vpn_01_connection" { + provider = restful.stackit + path = "${restful_resource.vpn_01_gateway.id}/connections" + body = { + displayName = "conn-to-azure" + tunnel1 = { + bgp = { + remoteAsn = 64513 + } + peering = { + localAddress = "169.254.21.1" + remoteAddress = "169.254.21.2" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = azurerm_public_ip.azure_gw_pip1.ip_address + } + tunnel2 = { + bgp = { + remoteAsn = 64513 + } + peering = { + localAddress = "169.254.21.5" + remoteAddress = "169.254.21.6" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = azurerm_public_ip.azure_gw_pip2.ip_address + } + } + + lifecycle { + ignore_changes = [ + body.tunnel1.preSharedKey, + body.tunnel2.preSharedKey + ] + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" +} + +# Azure Test VM & NSG +resource "azurerm_network_security_group" "vm_nsg" { + name = "test-vm-nsg" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + + security_rule { + name = "Allow-STACKIT" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "10.10.0.0/16" + destination_address_prefix = "*" + } +} + +resource "azurerm_network_interface" "vm_nic" { + name = "test-vm-nic" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.azure_vm_subnet.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_network_interface_security_group_association" "vm_nsg_assoc" { + network_interface_id = azurerm_network_interface.vm_nic.id + network_security_group_id = azurerm_network_security_group.vm_nsg.id +} + +resource "azurerm_linux_virtual_machine" "azure_test_vm" { + name = "azure-vpn-test-vm" + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + size = "Standard_B1s" + admin_username = "azureuser" + admin_password = "VpnTestPassw0rd!" + disable_password_authentication = false + + network_interface_ids = [ + azurerm_network_interface.vm_nic.id, + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + source_image_reference { + publisher = "Canonical" + offer = "0001-com-ubuntu-server-jammy" + sku = "22_04-lts" + version = "latest" + } +} + +# Outputs +output "vpn01_public_ip" { + value = module.vpn_sna_01.machine_public_ip + description = "Connect here via SSH to ping Azure" +} + +output "vpn01_private_ip" { + value = module.vpn_sna_01.machine_private_ipv4 +} + +output "azure_test_vm_private_ip" { + value = azurerm_linux_virtual_machine.azure_test_vm.private_ip_address +} + +# Command to run a ping test without SSHing +output "azure_run_command_ping_test" { + value = "az vm run-command invoke --resource-group ${azurerm_resource_group.rg.name} --name ${azurerm_linux_virtual_machine.azure_test_vm.name} --command-id RunShellScript --scripts 'ping -c 4 ${module.vpn_sna_01.machine_private_ipv4}'" + description = "Copy and paste this in your terminal to securely ping from the Azure VM to the STACKIT VM." +} diff --git a/examples/vpn-usecases/stackit-azure/MAINTAINERS.md b/examples/vpn-usecases/stackit-azure/MAINTAINERS.md new file mode 100644 index 0000000..1aaefce --- /dev/null +++ b/examples/vpn-usecases/stackit-azure/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (mauritz.uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/vpn-usecases/stackit-azure/README.md b/examples/vpn-usecases/stackit-azure/README.md new file mode 100644 index 0000000..626bac6 --- /dev/null +++ b/examples/vpn-usecases/stackit-azure/README.md @@ -0,0 +1,48 @@ +# STACKIT-to-Azure HA VPN Gateway + +> ⚠️ azurerm_virtual_network_gateway.azure_gateway takes between 30-90mins + +This example demonstrates how to establish a secure, Highly Available (HA) IPsec VPN connection between a STACKIT Network Area (SNA) and Microsoft Azure. + +The connection uses **BGP (Border Gateway Protocol)** via an Azure Virtual Network Gateway (Active-Active mode) to automatically exchange and propagate routes dynamically between the two cloud environments. + +## Architecture + +This Terraform configuration provisions the following resources: + +- **STACKIT:** An SNA, a debug machine, and an HA VPN Gateway (`ASN 64512`). +- **Azure:** A Resource Group, VNet, dedicated `GatewaySubnet`, an Active-Active Virtual Network Gateway (`ASN 64513`), two Local Network Gateways (representing STACKIT), and a private Ubuntu test VM. +- **VPN Connection:** Two redundant IPsec tunnels using dynamically generated PSKs and Link-Local BGP peering (`169.254.x.x/30`). +- **Security:** Azure Network Security Group (NSG) rules configured to allow inbound ICMP/SSH traffic specifically from the STACKIT network range. + +## Prerequisites + +- Configured STACKIT and Azure provider credentials. +- Azure CLI (`az`) installed and authenticated for testing from the Azure side. + +## Outputs + +Once the deployment is complete, Terraform will output the following information to help you verify connectivity: + +- `vpn01_public_ip`: The public IP of the debug machine in STACKIT. +- `vpn01_private_ip`: The private IP of the debug machine in STACKIT. +- `azure_test_vm_private_ip`: The private IP of the test VM in Azure. +- `azure_run_command_ping_test`: A pre-formatted Azure CLI command to test the connection. + +## How to Test the Connection + +You can verify the bi-directional tunnel is fully operational by following these steps: + +### 1. Test from Azure to STACKIT (Zero-Config) + +We utilize the Azure "Run Command" feature to execute a ping test directly inside the private Azure VM without needing SSH access or a Bastion host. + +Copy the command generated by the `azure_run_command_ping_test` output and run it in your terminal: + +```bash +az vm run-command invoke \ + --resource-group rg-vpn-test \ + --name azure-vpn-test-vm \ + --command-id RunShellScript \ + --scripts 'ping -c 4 ' +``` diff --git a/examples/vpn-usecases/stackit-gcp/.terraform.lock.hcl b/examples/vpn-usecases/stackit-gcp/.terraform.lock.hcl new file mode 100644 index 0000000..a510f99 --- /dev/null +++ b/examples/vpn-usecases/stackit-gcp/.terraform.lock.hcl @@ -0,0 +1,83 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "7.32.0" + hashes = [ + "h1:hDMENgq6nxoM6ttxN1HNrqbYiyRV8avLmUuUe4QWvKY=", + "zh:091afeeeb58035f26ebaec34755a15d56e3229c4ce6db2745c52ba2593204a30", + "zh:15d6a375c49d023dd21e612610b12bf79fbc6459bc5ad64b989d2180e9931f7d", + "zh:2c70ee949b01c0c7925618e36417ac5b9c1de91c66bb0bd956b2b2bd1d38a2b6", + "zh:2e531cf6f3af847104df65675ebd9c9a7450ba91d8d21ff6ed04eeab6a5684e2", + "zh:42fece780ef909136213762731c945d59c58dbaf92f64a46989102da9ebfa998", + "zh:9008b13bec8c588ef41ec813c3d67d26acb22ada241d9dd9ed408687607726cc", + "zh:a62e09bd551de8ea74b68c1eb44f9d7d0dc56957811915d46ec0a28254a30e0b", + "zh:ad3d4419561d19e88b72ad4bedbbc73ee77f20acb594261be8f716bf1a89f947", + "zh:af0c23df89e5fb815751c64c9a438527d6e0609df52fa7e281dbd90aa238270b", + "zh:b4d1157559d04792441550ae79f13d16bd7cc3f80a9616cd9ef3f83466564ce4", + "zh:db32528838dc9641981769012bdabf21e658756f1581ccbcd239aab0eb4aed11", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.8.1" + hashes = [ + "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", + "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", + "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", + "zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57", + "zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0", + "zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66", + "zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9", + "zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05", + "zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8", + "zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b", + "zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699", + ] +} + +provider "registry.terraform.io/magodo/restful" { + version = "0.25.2" + hashes = [ + "h1:gvoDTFfxp7n1B4Wsnx9IC7Ku8g8tdVR4mCC6TDX0Mws=", + "zh:0513ff62fce41a59462f39e1c4636f3c87e6f8d24ee579075900d3e0f57f6992", + "zh:1a3e39e6b8c7fd0f3983730944a029db8f00557922e337cff0567a07c5e74b45", + "zh:2527c96fcc45458efc9eca1c66cee98269d80693b571c57baee783402bfbaa28", + "zh:50cec9afe8b55629d1c94d477b26ff95de8cc8e3304f6c2bfc5dad3bccc6decc", + "zh:89e94c0f312d0ef4213b46ee776a27f6a5d114520c08a4716f4fee4c26c16f91", + "zh:9a9762ebaf9567a4aa34a1911f051527696241679e087137fcc7821e52b66483", + "zh:a065be3488e24928199904f4a496974c03fdcf2b06fccf016e405b3068d5ef76", + "zh:c62a1a6fb3c5135451f68ea4ed1f66d999ab654323d10526756e83f6f77d6bdf", + "zh:cf01364f89b713dc10eb87098839317e6f2de222bec2597923cddbb07bdd9c13", + "zh:dc0ac6a1e5e3199e1d35fb49f9de1d9325caa3c0d3e87ea8128295e19ac941c3", + "zh:e55cf6e8230f081b7c8ade592c14f1b8b45ee0aaa14c2bde2da9531d819a4392", + "zh:f333748916e68050c8935d760d6b9b469dd76eb94363af93562cbd076dba6ff5", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", + "zh:fdfdca8b7976c1a8b1b6a3589b4bfec277beb6dfb40c5568271d42f0b2f88a9d", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.95.0" + constraints = ">= 0.66.0, >= 0.87.0" + hashes = [ + "h1:sKmc6SGKEFglXKLMtOluJkFm7tzQZKQV3/QxUbHug1E=", + "zh:023edbb8ca984233bb51605a9005d4f7cb3365f0b11ddd68d911a1e30ccf64be", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:1fc43ed3055c4912e5b3ae2eba49dd5407beaa9ba6617612f317543f7d26ccd7", + "zh:31e587a9f279661b74b139e2a964a7d1c54a4073d27d21c2f948e0e7ba4c0d04", + "zh:37427a23800dff84c1b89d4985cb935c0112c59acd716d8920c160221c459061", + "zh:3a575f5c7d1252d99aea9187923087e1d483b2b34e42c9f058f557ec28c45d84", + "zh:44c7ee340e1a09d6f9a9873959f8283f0d73aee4a8c884f7b36985f943874b65", + "zh:4fab8fe953a0d4c21589cd36d23afe072c6403e37620c82839f9f829139cbbbf", + "zh:69fc061b3c7ea82d9e9a31d3665a535f6bb9dc3d6ff5b466f940d3d04a105e19", + "zh:85ee00442eff70ea103a96276efb5e1485b661b0a1db08bcdbd28b11b1f966e6", + "zh:9761ef1321c93cb3e4bc2499995b3e7ae910e2ae68b3228164dea73e9687ddb5", + "zh:b158a4e4726a4d4c9f61c5dc71abb4ca3a621269e1d7af88ab36d34c3bcec66f", + "zh:da37df9a426d83da8f6c1340104a6b9a7eef4a7e2589d0b70a89c574a1b3cc78", + "zh:e6421b9a351b2c9b2ab2341f2c07d863eb2ed055b847ea839de96b0fd62baf97", + ] +} diff --git a/examples/vpn-usecases/stackit-gcp/010-provider.tf b/examples/vpn-usecases/stackit-gcp/010-provider.tf new file mode 100644 index 0000000..eb7fb81 --- /dev/null +++ b/examples/vpn-usecases/stackit-gcp/010-provider.tf @@ -0,0 +1,56 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.95.0" + } + restful = { + source = "magodo/restful" + } + google = { + source = "hashicorp/google" + version = "7.32.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + enable_beta_resources = true +} + +provider "google" { + project = var.gcp_project + region = "europe-west4" + zone = "europe-west4-a" + credentials = file(var.gcp_service_account_key_path) +} + +ephemeral "stackit_access_token" "this" {} + +provider "restful" { + alias = "stackit" + base_url = "https://vpn.api.eu01.stackit.cloud" + security = { + http = { + token = { + token = ephemeral.stackit_access_token.this.access_token + } + } + } +} diff --git a/examples/vpn-usecases/stackit-gcp/020-variables.tf b/examples/vpn-usecases/stackit-gcp/020-variables.tf new file mode 100644 index 0000000..474bdbc --- /dev/null +++ b/examples/vpn-usecases/stackit-gcp/020-variables.tf @@ -0,0 +1,40 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# load via *.auto.tfvars + +variable "stackit_org_id" { + type = string +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string +} + +variable "gcp_service_account_key_path" { + type = string +} + +variable "gcp_project" { + type = string +} + +variable "stackit_admin_email" { + type = string +} diff --git a/examples/vpn-usecases/stackit-gcp/030-stackit-gcp-vpn.tf b/examples/vpn-usecases/stackit-gcp/030-stackit-gcp-vpn.tf new file mode 100644 index 0000000..5f82603 --- /dev/null +++ b/examples/vpn-usecases/stackit-gcp/030-stackit-gcp-vpn.tf @@ -0,0 +1,305 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# STACKIT Side (vpn-sna-01) +module "vpn_sna_01" { + source = "../module/stackit-sna-with-debug-machine" + machine_availability_zone = "eu01-1" + machine_ipv4_prefix = "10.10.10.0/24" + machine_network_name = "vpn-sna-01" + sna_name = "vpn-sna-01" + machine_name = "vpn-sna-01" + stackit_admin_email = var.stackit_admin_email + stackit_org_id = var.stackit_org_id + stackit_project_name = "vpn-sna-01" + sna_network_range_prefix = [ + "10.10.0.0/16" + ] +} + +resource "restful_resource" "vpn_01_gateway" { + provider = restful.stackit + path = "/v1/projects/${module.vpn_sna_01.project_id}/regions/eu01/gateways" + body = { + availabilityZones = { + tunnel1 = "eu01-1" + tunnel2 = "eu01-2" + } + bgp = { + localAsn = 64512 + overrideAdvertisedRoutes = ["10.10.0.0/16"] + } + displayName = "vpn01" + labels = null + planId = "p500" + routingType = "BGP_ROUTE_BASED" + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" +} + +data "restful_resource" "vpn_01_gateway_status" { + provider = restful.stackit + id = "${restful_resource.vpn_01_gateway.id}/status" +} + +resource "random_password" "vpn_psk" { + length = 32 + special = false +} + +# GCP VPC and Subnet +resource "google_compute_network" "gcp_vpc" { + name = "gcp-vpn-network" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "gcp_subnet" { + name = "gcp-vpn-subnet" + ip_cidr_range = "10.11.0.0/16" + region = "europe-west4" + network = google_compute_network.gcp_vpc.id +} + +# GCP HA VPN Gateway +resource "google_compute_ha_vpn_gateway" "gcp_gateway" { + name = "gcp-ha-vpn" + network = google_compute_network.gcp_vpc.id + region = "europe-west4" +} + +# GCP Cloud Router (for BGP) +resource "google_compute_router" "gcp_router" { + name = "gcp-router" + network = google_compute_network.gcp_vpc.name + region = "europe-west4" + bgp { + asn = 64513 # GCP's local ASN + } +} + +# GCP External VPN Gateway (Represents STACKIT in GCP) +resource "google_compute_external_vpn_gateway" "stackit_gateway" { + name = "stackit-external-gw" + redundancy_type = "TWO_IPS_REDUNDANCY" + description = "STACKIT VPN Gateway" + + # Fetching the public IPs from STACKIT + interface { + id = 0 + ip_address = data.restful_resource.vpn_01_gateway_status.output.tunnels[0].publicIP + } + interface { + id = 1 + ip_address = data.restful_resource.vpn_01_gateway_status.output.tunnels[1].publicIP + } +} + +# GCP VPN Tunnels +resource "google_compute_vpn_tunnel" "gcp_tunnel1" { + name = "gcp-tunnel-1" + region = "europe-west4" + vpn_gateway = google_compute_ha_vpn_gateway.gcp_gateway.id + peer_external_gateway = google_compute_external_vpn_gateway.stackit_gateway.id + peer_external_gateway_interface = 0 + shared_secret = random_password.vpn_psk.result + router = google_compute_router.gcp_router.id + vpn_gateway_interface = 0 +} + +resource "google_compute_vpn_tunnel" "gcp_tunnel2" { + name = "gcp-tunnel-2" + region = "europe-west4" + vpn_gateway = google_compute_ha_vpn_gateway.gcp_gateway.id + peer_external_gateway = google_compute_external_vpn_gateway.stackit_gateway.id + peer_external_gateway_interface = 1 + shared_secret = random_password.vpn_psk.result + router = google_compute_router.gcp_router.id + vpn_gateway_interface = 1 +} + +# GCP Cloud Router Interfaces & BGP Peers +resource "google_compute_router_interface" "gcp_router_interface1" { + name = "gcp-interface-1" + router = google_compute_router.gcp_router.name + region = "europe-west4" + ip_range = "169.254.0.2/30" # GCP's local BGP IP + vpn_tunnel = google_compute_vpn_tunnel.gcp_tunnel1.name +} + +resource "google_compute_router_peer" "gcp_router_peer1" { + name = "gcp-peer-1" + router = google_compute_router.gcp_router.name + region = "europe-west4" + peer_ip_address = "169.254.0.1" # STACKIT's local BGP IP + peer_asn = 64512 # STACKIT's ASN + advertised_route_priority = 100 + interface = google_compute_router_interface.gcp_router_interface1.name +} + +resource "google_compute_router_interface" "gcp_router_interface2" { + name = "gcp-interface-2" + router = google_compute_router.gcp_router.name + region = "europe-west4" + ip_range = "169.254.1.2/30" + vpn_tunnel = google_compute_vpn_tunnel.gcp_tunnel2.name +} + +resource "google_compute_router_peer" "gcp_router_peer2" { + name = "gcp-peer-2" + router = google_compute_router.gcp_router.name + region = "europe-west4" + peer_ip_address = "169.254.1.1" + peer_asn = 64512 + advertised_route_priority = 100 + interface = google_compute_router_interface.gcp_router_interface2.name +} + +# Connection from STACKIT to GCP +resource "restful_resource" "vpn_01_connection" { + provider = restful.stackit + path = "${restful_resource.vpn_01_gateway.id}/connections" + body = { + displayName = "conn-to-gcp" + tunnel1 = { + bgp = { + remoteAsn = 64513 + } + peering = { + localAddress = "169.254.0.1" + remoteAddress = "169.254.0.2" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = google_compute_ha_vpn_gateway.gcp_gateway.vpn_interfaces[0].ip_address + } + tunnel2 = { + bgp = { + remoteAsn = 64513 + } + peering = { + localAddress = "169.254.1.1" + remoteAddress = "169.254.1.2" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = google_compute_ha_vpn_gateway.gcp_gateway.vpn_interfaces[1].ip_address + } + } + + lifecycle { + ignore_changes = [ + body.tunnel1.preSharedKey, + body.tunnel2.preSharedKey + ] + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" +} + +# GCP Test VM & Firewall Rules +# Firewall: Allow Identity-Aware Proxy (IAP) to SSH into the VM +resource "google_compute_firewall" "allow_iap_ssh" { + name = "allow-iap-ssh" + network = google_compute_network.gcp_vpc.name + + allow { + protocol = "tcp" + ports = ["22"] + } + + source_ranges = ["35.235.240.0/20"] + target_tags = ["test-vm"] +} + +# Firewall: Allow STACKIT to ping/SSH into the GCP VM over the VPN +resource "google_compute_firewall" "allow_stackit_vpn_traffic" { + name = "allow-stackit-vpn-traffic" + network = google_compute_network.gcp_vpc.name + + allow { + protocol = "icmp" + } + allow { + protocol = "tcp" + ports = ["22"] + } + + # Your STACKIT SNA range + source_ranges = ["10.10.0.0/16"] + target_tags = ["test-vm"] +} + +# GCP Virtual Machine +resource "google_compute_instance" "gcp_test_vm" { + name = "gcp-vpn-test-vm" + machine_type = "e2-micro" + zone = "europe-west4-a" + + tags = ["test-vm"] + + boot_disk { + initialize_params { + image = "debian-cloud/debian-12" + } + } + + network_interface { + subnetwork = google_compute_subnetwork.gcp_subnet.id + # Omitting the 'access_config' block ensures this VM gets NO public IP address. + } +} + +# Outputs +output "vpn01_public_ip" { + value = module.vpn_sna_01.machine_public_ip +} + +output "vpn01_private_ip" { + value = module.vpn_sna_01.machine_private_ipv4 +} + +output "gcp_test_vm_private_ip" { + value = google_compute_instance.gcp_test_vm.network_interface[0].network_ip +} + +output "gcp_iap_command" { + value = "gcloud compute ssh ${google_compute_instance.gcp_test_vm.name} --tunnel-through-iap" +} diff --git a/examples/vpn-usecases/stackit-gcp/MAINTAINERS.md b/examples/vpn-usecases/stackit-gcp/MAINTAINERS.md new file mode 100644 index 0000000..1aaefce --- /dev/null +++ b/examples/vpn-usecases/stackit-gcp/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (mauritz.uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/vpn-usecases/stackit-gcp/README.md b/examples/vpn-usecases/stackit-gcp/README.md new file mode 100644 index 0000000..d0c9f44 --- /dev/null +++ b/examples/vpn-usecases/stackit-gcp/README.md @@ -0,0 +1,48 @@ +# STACKIT-to-GCP HA VPN Gateway + +This example demonstrates how to establish a secure, Highly Available (HA) IPsec VPN connection between a STACKIT Network Area (SNA) and Google Cloud Platform (GCP). + +The connection uses **BGP (Border Gateway Protocol)** via a GCP Cloud Router to automatically exchange and propagate routes dynamically between the two cloud environments. + +## Architecture + +This Terraform configuration provisions the following resources: + +- **STACKIT:** An SNA, a debug machine, and an HA VPN Gateway (`ASN 64512`). +- **GCP:** A VPC, a Subnet (`europe-west4`), an HA VPN Gateway, a Cloud Router (`ASN 64513`), and a private Debian test VM. +- **VPN Connection:** Two redundant IPsec tunnels using dynamically generated PSKs and Link-Local BGP peering (`169.254.x.x/30`). +- **Security:** GCP Firewall rules configured to allow Identity-Aware Proxy (IAP) SSH access and inbound ICMP/SSH traffic from the STACKIT network. + +## Prerequisites + +- Configured STACKIT and GCP provider credentials. +- Google Cloud SDK (`gcloud` CLI) installed and authenticated for IAP testing. + +## How to Test the Connection + +Once `terraform apply` is complete, check the generated outputs. You can verify the bi-directional tunnel is fully operational by following these steps: + +### 1. Test from GCP to STACKIT + +Connect to the private GCP VM using Google's Identity-Aware Proxy (IAP) and ping the STACKIT debug machine. + +```bash +# 1. SSH into the GCP test VM (copy this from the `gcp_iap_command` output) +gcloud compute ssh gcp-vpn-test-vm --zone=europe-west4-a --tunnel-through-iap + +# 2. Ping the STACKIT private IP (copy from the `vpn01_private_ip` output) +ping +``` + +### 2. Test from STACKIT to GCP + +Connect to the STACKIT debug machine using its public IP, then ping the private GCP VM across the VPN tunnel. + +```Bash +# 1. SSH into the STACKIT debug machine +ssh debug@ +# password debug123 + +# 2. Ping the GCP private IP (copy from the `gcp_test_vm_private_ip` output) +ping +``` diff --git a/examples/vpn-usecases/stackit-stackit/.terraform.lock.hcl b/examples/vpn-usecases/stackit-stackit/.terraform.lock.hcl new file mode 100644 index 0000000..489e1ef --- /dev/null +++ b/examples/vpn-usecases/stackit-stackit/.terraform.lock.hcl @@ -0,0 +1,87 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/random" { + version = "3.8.1" + hashes = [ + "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", + "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", + "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", + "zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57", + "zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0", + "zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66", + "zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9", + "zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05", + "zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8", + "zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b", + "zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699", + ] +} + +provider "registry.terraform.io/magodo/restful" { + version = "0.25.2" + hashes = [ + "h1:gvoDTFfxp7n1B4Wsnx9IC7Ku8g8tdVR4mCC6TDX0Mws=", + "zh:0513ff62fce41a59462f39e1c4636f3c87e6f8d24ee579075900d3e0f57f6992", + "zh:1a3e39e6b8c7fd0f3983730944a029db8f00557922e337cff0567a07c5e74b45", + "zh:2527c96fcc45458efc9eca1c66cee98269d80693b571c57baee783402bfbaa28", + "zh:50cec9afe8b55629d1c94d477b26ff95de8cc8e3304f6c2bfc5dad3bccc6decc", + "zh:89e94c0f312d0ef4213b46ee776a27f6a5d114520c08a4716f4fee4c26c16f91", + "zh:9a9762ebaf9567a4aa34a1911f051527696241679e087137fcc7821e52b66483", + "zh:a065be3488e24928199904f4a496974c03fdcf2b06fccf016e405b3068d5ef76", + "zh:c62a1a6fb3c5135451f68ea4ed1f66d999ab654323d10526756e83f6f77d6bdf", + "zh:cf01364f89b713dc10eb87098839317e6f2de222bec2597923cddbb07bdd9c13", + "zh:dc0ac6a1e5e3199e1d35fb49f9de1d9325caa3c0d3e87ea8128295e19ac941c3", + "zh:e55cf6e8230f081b7c8ade592c14f1b8b45ee0aaa14c2bde2da9531d819a4392", + "zh:f333748916e68050c8935d760d6b9b469dd76eb94363af93562cbd076dba6ff5", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", + "zh:fdfdca8b7976c1a8b1b6a3589b4bfec277beb6dfb40c5568271d42f0b2f88a9d", + ] +} + +provider "registry.terraform.io/mastercard/restapi" { + version = "3.0.0" + constraints = ">= 3.0.0" + hashes = [ + "h1:y1I3azDHOqRySTyDHsb3Xh1waP/99KfykZRagbRx1qI=", + "zh:0b63bd3c25a31f090a41933f90b7dd6e984add1c4261d8f5caa73f4d5aa065a4", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:2d31f322454d271eb328c2d3b3d41f426df98503982788be347799ddf68bf9bf", + "zh:47dd97e3f43bb89ae4254bba90ffbc6d521338554a1f94961e21214dd801b81b", + "zh:49636b072b9a30d15916468857bce91d39bc87bbba1c99fb3894fafa9409b8b4", + "zh:5566605a8e16478bc66c1fec8dea0890586c084221161dc82b73d162d44c08a7", + "zh:5859e0ad05aa6b3b108f0b718986e237a18d5176efea62d1ac1ef352561b4713", + "zh:76129b89e2b56d8d2af8f6e10cc748bea4ee6ec1105e916f1254cd124f4dcf9c", + "zh:bfc20b5fd03cb3243917e8cf360e5208284e757ab82f83c992da471ef16a0eab", + "zh:d1d2363009253cdfe5795a48b6412bff11104fe6a52fb0a57e5a95fc765a161e", + "zh:d1f0b981089ad709b73c4f989a9cd9118c4e3cb8fc0a2b303aa4d77cc5102a53", + "zh:dbfddb2f407481a4e88fdc17739c805d9d9fff2451efcb9226572d59ed2e9128", + "zh:df04a8c777d05896684171807b27c41befbf5f217f50b0e9b2b27164d4aacca5", + "zh:e68b450c66efe55d1132585477fa71207680806edafb3792ca44d9695d0a1d75", + "zh:f894e7e9913347e25e67d5d3bf91659c06877dd5fa11acf75820fa03fa34b8bd", + ] +} + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.95.0" + constraints = ">= 0.66.0" + hashes = [ + "h1:sKmc6SGKEFglXKLMtOluJkFm7tzQZKQV3/QxUbHug1E=", + "zh:023edbb8ca984233bb51605a9005d4f7cb3365f0b11ddd68d911a1e30ccf64be", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:1fc43ed3055c4912e5b3ae2eba49dd5407beaa9ba6617612f317543f7d26ccd7", + "zh:31e587a9f279661b74b139e2a964a7d1c54a4073d27d21c2f948e0e7ba4c0d04", + "zh:37427a23800dff84c1b89d4985cb935c0112c59acd716d8920c160221c459061", + "zh:3a575f5c7d1252d99aea9187923087e1d483b2b34e42c9f058f557ec28c45d84", + "zh:44c7ee340e1a09d6f9a9873959f8283f0d73aee4a8c884f7b36985f943874b65", + "zh:4fab8fe953a0d4c21589cd36d23afe072c6403e37620c82839f9f829139cbbbf", + "zh:69fc061b3c7ea82d9e9a31d3665a535f6bb9dc3d6ff5b466f940d3d04a105e19", + "zh:85ee00442eff70ea103a96276efb5e1485b661b0a1db08bcdbd28b11b1f966e6", + "zh:9761ef1321c93cb3e4bc2499995b3e7ae910e2ae68b3228164dea73e9687ddb5", + "zh:b158a4e4726a4d4c9f61c5dc71abb4ca3a621269e1d7af88ab36d34c3bcec66f", + "zh:da37df9a426d83da8f6c1340104a6b9a7eef4a7e2589d0b70a89c574a1b3cc78", + "zh:e6421b9a351b2c9b2ab2341f2c07d863eb2ed055b847ea839de96b0fd62baf97", + ] +} diff --git a/examples/vpn-usecases/stackit-stackit/010-provider.tf b/examples/vpn-usecases/stackit-stackit/010-provider.tf new file mode 100644 index 0000000..6e6d261 --- /dev/null +++ b/examples/vpn-usecases/stackit-stackit/010-provider.tf @@ -0,0 +1,50 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.95.0" + } + restapi = { + source = "Mastercard/restapi" + version = ">= 3.0.0" + } + restful = { + source = "magodo/restful" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + enable_beta_resources = true + experiments = ["iam"] +} + +ephemeral "stackit_access_token" "this" {} + +provider "restful" { + alias = "stackit" + base_url = "https://vpn.api.eu01.stackit.cloud" + security = { + http = { + token = { + token = ephemeral.stackit_access_token.this.access_token + } + } + } +} diff --git a/examples/vpn-usecases/stackit-stackit/020-variables.tf b/examples/vpn-usecases/stackit-stackit/020-variables.tf new file mode 100644 index 0000000..a0f733d --- /dev/null +++ b/examples/vpn-usecases/stackit-stackit/020-variables.tf @@ -0,0 +1,32 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# load via *.auto.tfvars + +variable "stackit_org_id" { + type = string +} + +variable "stackit_region" { + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + type = string +} + +variable "stackit_admin_email" { + type = string +} diff --git a/examples/vpn-usecases/stackit-stackit/030-stackit-stackit-vpn.tf b/examples/vpn-usecases/stackit-stackit/030-stackit-stackit-vpn.tf new file mode 100644 index 0000000..42c60eb --- /dev/null +++ b/examples/vpn-usecases/stackit-stackit/030-stackit-stackit-vpn.tf @@ -0,0 +1,255 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module "vpn_sna_01" { + source = "../module/stackit-sna-with-debug-machine" + machine_availability_zone = "eu01-1" + machine_ipv4_prefix = "10.10.10.0/24" + machine_network_name = "vpn-sna-01" + sna_name = "vpn-sna-01" + machine_name = "vpn-sna-01" + stackit_admin_email = var.stackit_admin_email + stackit_org_id = var.stackit_org_id + stackit_project_name = "vpn-sna-01" + sna_network_range_prefix = [ + "10.10.0.0/16" + ] +} + +module "vpn_sna_02" { + source = "../module/stackit-sna-with-debug-machine" + machine_availability_zone = "eu01-2" + machine_ipv4_prefix = "10.11.11.0/24" + machine_network_name = "vpn-sna-02" + machine_name = "vpn-sna-02" + sna_name = "vpn-sna-02" + stackit_admin_email = var.stackit_admin_email + stackit_org_id = var.stackit_org_id + stackit_project_name = "vpn-sna-02" + sna_network_range_prefix = [ + "10.11.0.0/16" + ] +} + +# Gateway 1 (vpn-sna-01) +resource "restful_resource" "vpn_01_gateway" { + provider = restful.stackit + path = "/v1/projects/${module.vpn_sna_01.project_id}/regions/eu01/gateways" + body = { + availabilityZones = { + tunnel1 = "eu01-1" + tunnel2 = "eu01-2" + } + bgp = { + localAsn = 64512 + overrideAdvertisedRoutes = ["10.10.0.0/16"] + } + displayName = "vpn01" + labels = null + planId = "p500" + routingType = "BGP_ROUTE_BASED" + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" +} + +data "restful_resource" "vpn_01_gateway_status" { + provider = restful.stackit + id = "${restful_resource.vpn_01_gateway.id}/status" +} + +# Gateway 2 (vpn-sna-02) +resource "restful_resource" "vpn_02_gateway" { + provider = restful.stackit + path = "/v1/projects/${module.vpn_sna_02.project_id}/regions/eu01/gateways" + body = { + availabilityZones = { + tunnel1 = "eu01-1" + tunnel2 = "eu01-2" + } + bgp = { + localAsn = 64513 + overrideAdvertisedRoutes = ["10.11.0.0/16"] + } + displayName = "vpn02" + labels = null + planId = "p500" + routingType = "BGP_ROUTE_BASED" + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" +} + +data "restful_resource" "vpn_02_gateway_status" { + provider = restful.stackit + id = "${restful_resource.vpn_02_gateway.id}/status" +} + +# Shared VPN Credentials +resource "random_password" "vpn_psk" { + length = 32 + special = false +} + +# Connection from Gateway 1 to Gateway 2 +resource "restful_resource" "vpn_01_connection" { + provider = restful.stackit + path = "${restful_resource.vpn_01_gateway.id}/connections" + body = { + displayName = "conn-to-vpn02" + tunnel1 = { + bgp = { + remoteAsn = 64513 + } + peering = { + localAddress = "169.254.0.1" + remoteAddress = "169.254.0.2" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = data.restful_resource.vpn_02_gateway_status.output.tunnels[0].publicIP + } + tunnel2 = { + bgp = { + remoteAsn = 64513 + } + peering = { + localAddress = "169.254.1.1" + remoteAddress = "169.254.1.2" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = data.restful_resource.vpn_02_gateway_status.output.tunnels[1].publicIP + } + } + + lifecycle { + ignore_changes = [ + body.tunnel1.preSharedKey, + body.tunnel2.preSharedKey + ] + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" +} + +# Connection from Gateway 2 to Gateway 1 +resource "restful_resource" "vpn_02_connection" { + provider = restful.stackit + path = "${restful_resource.vpn_02_gateway.id}/connections" + body = { + displayName = "conn-to-vpn01" + tunnel1 = { + bgp = { + remoteAsn = 64512 + } + peering = { + localAddress = "169.254.0.2" + remoteAddress = "169.254.0.1" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = data.restful_resource.vpn_01_gateway_status.output.tunnels[0].publicIP + } + tunnel2 = { + bgp = { + remoteAsn = 64512 + } + peering = { + localAddress = "169.254.1.2" + remoteAddress = "169.254.1.1" + } + phase1 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + phase2 = { + dhGroups = ["modp2048"] + encryptionAlgorithms = ["aes256gcm16"] + integrityAlgorithms = ["sha2_256"] + } + preSharedKey = random_password.vpn_psk.result + remoteAddress = data.restful_resource.vpn_01_gateway_status.output.tunnels[1].publicIP + } + } + + read_path = "$(path)/$(body.id)" + update_path = "$(path)/$(body.id)" + update_method = "PUT" + delete_path = "$(path)/$(body.id)" + delete_method = "DELETE" + + lifecycle { + ignore_changes = [ + body.tunnel1.preSharedKey, + body.tunnel2.preSharedKey + ] + } +} + +output "vpn01_public_ip" { + value = module.vpn_sna_01.machine_public_ip +} + +output "vpn01_private_ip" { + value = module.vpn_sna_01.machine_private_ipv4 +} + +output "vpn02_public_ip" { + value = module.vpn_sna_02.machine_public_ip +} + +output "vpn02_private_ip" { + value = module.vpn_sna_02.machine_private_ipv4 +} diff --git a/examples/vpn-usecases/stackit-stackit/MAINTAINERS.md b/examples/vpn-usecases/stackit-stackit/MAINTAINERS.md new file mode 100644 index 0000000..1aaefce --- /dev/null +++ b/examples/vpn-usecases/stackit-stackit/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Mauritz Uphoff (mauritz.uphoff@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/vpn-usecases/stackit-stackit/README.md b/examples/vpn-usecases/stackit-stackit/README.md new file mode 100644 index 0000000..c091147 --- /dev/null +++ b/examples/vpn-usecases/stackit-stackit/README.md @@ -0,0 +1,21 @@ +# STACKIT-to-STACKIT VPN Gateway + +This example leverages the STACKIT VPN service to establish a secure, Highly Available (HA) connection between two separate STACKIT Network Areas (SNAs). + +The connection utilizes **BGP (Border Gateway Protocol)** to automatically propagate and learn routing information between the two networks. + +> **Note:** Currently, native SNA peering is not available in STACKIT. Therefore, provisioning a VPN connection is the required method to interconnect two SNAs. This will change in the future once native SNA peering is released. + +![Architecture Diagram](docs/architecture.png) + +## How to Test the Connection + +Once the deployment is complete, you can verify the VPN tunnel using the provisioned debug machines: + +1. **SSH** into the first debug machine using its public IP (`vpn01_public_ip`). +2. **Ping** the private IP of the second debug machine (`vpn02_private_ip`) across the tunnel. + +```bash +# Example test command once connected to the vpn01 machine via SSH +ping +``` diff --git a/examples/vpn-usecases/stackit-stackit/docs/architecture.png b/examples/vpn-usecases/stackit-stackit/docs/architecture.png new file mode 100644 index 0000000..fe965d1 Binary files /dev/null and b/examples/vpn-usecases/stackit-stackit/docs/architecture.png differ diff --git a/modules/test-machine/main.tf b/modules/test-machine/main.tf index 2bf7f22..4547645 100644 --- a/modules/test-machine/main.tf +++ b/modules/test-machine/main.tf @@ -12,14 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -terraform { - required_providers { - stackit = { - source = "stackitcloud/stackit" - } - } -} - resource "stackit_volume" "boot_volume" { project_id = var.project_id name = "${var.name}-volume" diff --git a/modules/test-machine/variables.tf b/modules/test-machine/variables.tf index a85ae88..6813331 100644 --- a/modules/test-machine/variables.tf +++ b/modules/test-machine/variables.tf @@ -15,40 +15,70 @@ variable "project_id" { description = "The STACKIT Project ID" type = string + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$", var.project_id)) + error_message = "The project_id must be a valid UUID." + } } variable "network_id" { description = "The Network ID (UUID) where the machine should be spawned" type = string + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$", var.network_id)) + error_message = "The network_id must be a valid UUID." + } } variable "name" { description = "Hostname of the server" type = string default = "test-machine" + + validation { + condition = can(regex("^[a-z0-9-]+$", var.name)) + error_message = "The machine name must contain only lowercase letters, numbers, and hyphens." + } } variable "availability_zone" { description = "The availability zone (e.g. eu01-1)" type = string + + validation { + condition = can(regex("^[a-z]{2}[0-9]{2}-[a-zA-Z0-9]+$", var.availability_zone)) + error_message = "The availability zone must follow the STACKIT pattern (e.g., eu01-1, eu01-m)." + } } variable "machine_type" { description = "Flavor of the machine" type = string - default = "g1.1" + default = "c2i.1" } variable "image_id" { description = "Image UUID (Default: Debian 12)" type = string default = "c751cde7-e648-4f81-9722-ce9c7848bed0" + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$", var.image_id)) + error_message = "The image_id must be a valid UUID." + } } variable "disk_size" { description = "Boot volume size in GB" type = number default = 50 + + validation { + condition = var.disk_size >= 1 + error_message = "The disk_size must be at least 1 GB." + } } variable "disk_performance_class" { diff --git a/modules/test-ske/README.md b/modules/test-ske/README.md new file mode 100644 index 0000000..6c23eb5 --- /dev/null +++ b/modules/test-ske/README.md @@ -0,0 +1,54 @@ +# Test SKE Module + +This module is designed to quickly spin up an SKE cluster. Internally, we use it to +debug network connectivity and deploy test applications in a simple, frictionless manner. +It automatically selects the latest SKE and node pool machine versions. + + + +## Requirements + +| Name | Version | +| ------------------------------------------------------------------ | -------- | +| [random](#requirement_random) | 3.9.0 | +| [stackit](#requirement_stackit) | >=0.95.0 | + +## Providers + +| Name | Version | +| ------------------------------------------------------------ | -------- | +| [random](#provider_random) | 3.9.0 | +| [stackit](#provider_stackit) | >=0.95.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| [random_string.this](https://registry.terraform.io/providers/hashicorp/random/3.9.0/docs/resources/string) | 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 | +| [stackit_ske_kubernetes_versions.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/data-sources/ske_kubernetes_versions) | data source | +| [stackit_ske_machine_image_versions.this](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/data-sources/ske_machine_image_versions) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | +| [cluster_name](#input_cluster_name) | The name of the Kubernetes cluster | `string` | `null` | no | +| [maintenance](#input_maintenance) | Maintenance window configuration for the cluster |
object({
enable_kubernetes_version_updates = bool
enable_machine_image_version_updates = bool
start = string
end = string
})
|
{
"enable_kubernetes_version_updates": true,
"enable_machine_image_version_updates": true,
"end": "02:00:00Z",
"start": "01:00:00Z"
}
| no | +| [network_id](#input_network_id) | The ID of the STACKIT network in which the SKE cluster will be deployed. If not provided, the cluster will automatically create a network on demand. Specifying a network ID is only supported in SNA setups | `string` | `null` | no | +| [node_pools](#input_node_pools) | Configuration for the cluster node pools | `any` |
[
{
"availability_zones": [
"eu01-1",
"eu01-2",
"eu01-3"
],
"machine_type": "g2i.4",
"max_surge": 3,
"maximum": 3,
"minimum": 1,
"name": "standard",
"os_name": "flatcar",
"volume_size": 20,
"volume_type": "storage_premium_perf6"
}
]
| no | +| [project_id](#input_project_id) | The STACKIT project ID | `string` | n/a | yes | + +## Outputs + +| Name | Description | +| ----------------------------------------------------------------------- | --------------------------------------------- | +| [cluster_name](#output_cluster_name) | The name of the provisioned SKE cluster | +| [kubeconfig](#output_kubeconfig) | The kubeconfig contents to access the cluster | + + diff --git a/modules/test-ske/main.tf b/modules/test-ske/main.tf new file mode 100644 index 0000000..38bf7f4 --- /dev/null +++ b/modules/test-ske/main.tf @@ -0,0 +1,84 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +data "stackit_ske_kubernetes_versions" "this" { + version_state = "SUPPORTED" +} + +data "stackit_ske_machine_image_versions" "this" { + version_state = "SUPPORTED" +} + +resource "random_string" "this" { + length = 6 + special = false + upper = false +} + +locals { + flatcar_supported_versions = flatten([ + for mi in data.stackit_ske_machine_image_versions.this.machine_images : [ + for v in mi.versions : v.version if mi.name == "flatcar" + ] + ]) + flatcar_supported_version = length(local.flatcar_supported_versions) > 0 ? local.flatcar_supported_versions[0] : null + + ubuntu_supported_versions = flatten([ + for mi in data.stackit_ske_machine_image_versions.this.machine_images : [ + for v in mi.versions : v.version if mi.name == "ubuntu" + ] + ]) + ubuntu_supported_version = length(local.ubuntu_supported_versions) > 0 ? local.ubuntu_supported_versions[0] : null +} + +resource "stackit_ske_cluster" "this" { + project_id = var.project_id + name = var.cluster_name != null && var.cluster_name != "" ? var.cluster_name : "ske-${random_string.this.result}" + kubernetes_version_min = data.stackit_ske_kubernetes_versions.this.kubernetes_versions[0].version + + maintenance = var.maintenance + + network = var.network_id != null && var.network_id != "" ? { + id = var.network_id + } : null + + node_pools = [ + for np in var.node_pools : { + name = np.name + machine_type = np.machine_type + minimum = np.minimum + maximum = np.maximum + max_surge = np.max_surge + availability_zones = np.availability_zones + os_name = np.os_name + # Dynamically injects the latest OS version based on os_name if not explicitly set + os_version_min = lookup(np, "os_version_min", np.os_name == "flatcar" ? local.flatcar_supported_version : local.ubuntu_supported_version) + volume_size = np.volume_size + volume_type = np.volume_type + } + ] + + lifecycle { + ignore_changes = [ + kubernetes_version_min, + node_pools + ] + } +} + +resource "stackit_ske_kubeconfig" "this" { + project_id = var.project_id + cluster_name = stackit_ske_cluster.this.name + refresh = true +} diff --git a/modules/test-ske/outputs.tf b/modules/test-ske/outputs.tf new file mode 100644 index 0000000..3a16cc5 --- /dev/null +++ b/modules/test-ske/outputs.tf @@ -0,0 +1,24 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +output "cluster_name" { + description = "The name of the provisioned SKE cluster" + value = stackit_ske_cluster.this.name +} + +output "kubeconfig" { + description = "The kubeconfig contents to access the cluster" + value = stackit_ske_kubeconfig.this.kube_config + sensitive = true +} diff --git a/modules/test-ske/provider.tf b/modules/test-ske/provider.tf new file mode 100644 index 0000000..994578e --- /dev/null +++ b/modules/test-ske/provider.tf @@ -0,0 +1,26 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">=0.95.0" + } + random = { + source = "hashicorp/random" + version = "3.9.0" + } + } +} diff --git a/modules/test-ske/variables.tf b/modules/test-ske/variables.tf new file mode 100644 index 0000000..c029aa2 --- /dev/null +++ b/modules/test-ske/variables.tf @@ -0,0 +1,64 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "project_id" { + description = "The STACKIT project ID" + type = string +} + +variable "cluster_name" { + description = "The name of the Kubernetes cluster" + type = string + default = null +} + +variable "network_id" { + description = "The ID of the STACKIT network in which the SKE cluster will be deployed. If not provided, the cluster will automatically create a network on demand. Specifying a network ID is only supported in SNA setups" + type = string + default = null +} + +variable "maintenance" { + description = "Maintenance window configuration for the cluster" + type = object({ + enable_kubernetes_version_updates = bool + enable_machine_image_version_updates = bool + start = string + end = string + }) + default = { + enable_kubernetes_version_updates = true + enable_machine_image_version_updates = true + start = "01:00:00Z" + end = "02:00:00Z" + } +} + +variable "node_pools" { + description = "Configuration for the cluster node pools" + type = any + default = [ + { + name = "standard" + machine_type = "g2i.4" + minimum = 1 + maximum = 3 + max_surge = 3 + availability_zones = ["eu01-1", "eu01-2", "eu01-3"] + os_name = "flatcar" + volume_size = 20 + volume_type = "storage_premium_perf6" + } + ] +} diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..f81f308 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,329 @@ +# Scripts + +Helper scripts for working with STACKIT services. + +> **Warning:** These scripts can perform destructive or wide-reaching operations against your STACKIT account (e.g. deleting volumes, overwriting kubeconfigs, writing secrets). Review each script and understand what it does before running it. + +## Overview + +| Script | Purpose | Required tools | +| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | +| [`s3-ssec.sh`](#s3-ssec) | Upload and Download Files to a STACKIT S3 Bucket (Object Storage) and enable Server Side Encryption with Custom Keys. | `awscli`, `python3-awscrt` | +| [`check-stackit-ip.sh`](#check-stackit-ipsh) | Check whether a given IP address belongs to STACKIT's public IP ranges. | `stackit`, `jq`, `grepcidr` | +| [`create-kubeconfig-multiple-projects.sh`](#create-kubeconfig-multiple-projectssh) | Generate kubeconfig entries for every SKE cluster across one or more STACKIT projects. | `stackit`, `yq` | +| [`delete-unused-volumes.sh`](#delete-unused-volumessh) | Delete all STACKIT volumes whose status is `AVAILABLE` (i.e. not attached). | `stackit`, `yq` | +| [`list-project-resources.sh`](#list-project-resourcessh) | Render a Markdown inventory of resources (DNS, SKE, databases, storage, …) for one or more STACKIT projects. | `stackit`, `jq` | +| [`ske-show-versions.sh`](#ske-show-versionssh) | Print overview of SKE cluster Kubernetes versions and nodepool image versions, marking deprecated versions. | `stackit` (>= 0.59.0), `jq`, `awk` | +| [`smctl.sh`](#smctlsh) | Unified CLI wrapper around HashiCorp Vault for the STACKIT Secret Manager (KV v2), think `kubectl` for secrets | `vault`, `jq` | +| [`vault-migrate.sh`](#vault-migratesh) | Migrate secrets between two Vault instances using the KV v2 API (supports userpass and LDAP for source). | `vault`, `jq` | + +--- + +## `s3-ssec.sh` + +### STACKIT S3 Object Storage SSE-C Automation Script + +This script automates secure Server-Side Encryption with Customer-Provided Keys (SSE-C) using the native aws-cli tool against STACKIT Object Storage. + +### Why This Script Exists + +Standard S3 tools (s3cmd, rclone) often fail during SSE-C operations on Linux due to a Binary-to-Text Encoding Mismatch when passing keys via the command line. Furthermore, third-party tools frequently throw false alarms regarding data corruption because the server-side encrypted file's MD5 hash (ETag) no longer matches the local unencrypted file's hash. + +The Solution: This script utilizes the official aws-cli along with a binary key file reference (fileb://). This approach bypasses shell encoding issues entirely. The AWS CLI natively understands SSE-C protocols, managing hash verifications and setting all necessary headers automatically. + +### Prerequisites + +AWS CLI installed: + +```bash +sudo apt install awscli -y # Debian/Ubuntu +sudo dnf install aws-cli -y # RHEL/CentOS +``` + +Permissions: Root access (or appropriate sudo permissions) to store the encryption key safely under /root/ssec.key. + +### Setup & Configuration + +Open the script and fill in your STACKIT credentials and bucket name under the configuration block: + +```bash +export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY" +export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_ACCESS_KEY" +BUCKET="s3://your-bucket-name" +``` + +Note: On its very first run, the script will automatically generate a cryptographically secure 32-byte binary AES key using openssl and lock down its file permissions (chmod 400). + +### Usage + +Make the script executable: + +```bash +chmod +x s3-ssec.sh +``` + +1. Uploading a File (Encrypted) + To upload and encrypt a local file, pass upload followed by the file path: + +```bash +./s3-ssec.sh upload /path/to/local/file.txt +``` + +The file will be encrypted on the fly by the STACKIT storage backend using your generated key. + +2. Downloading a File (Decrypted) + To download and decrypt an object from the bucket, pass download followed by the remote object name: + +```bash +./s3-ssec.sh download file.txt +``` + +The script will fetch the file, provide the required key headers, and save the decrypted file locally as ./file.txt_downloaded. + +Security Warning ⚠️ +Backup your Key: If you lose the /root/ssec.key file, any data encrypted with it in the bucket cannot be recovered. + +Credential Rotation: Never commit the script to public repositories while it contains live AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY strings. Use environment variables or secret managers in production environments. + +--- + +## `check-stackit-ip.sh` + +Looks up the STACKIT public IP ranges (via `stackit curl https://iaas.api.eu01.stackit.cloud/v1/networks/public-ip-ranges`) and tells you whether a given IP falls inside any of them. Useful to verify whether a public-facing address actually originates from STACKIT. + +Exits `0` if the IP is found, `1` otherwise. + +### Example + +```bash +# Check a single IP +./check-stackit-ip.sh 45.129.40.1 +``` + +Sample output: + +``` +Fetching STACKIT IP ranges... +Found: 45.129.40.1 is in the range 45.129.40.0/22 +``` + +--- + +## `create-kubeconfig-multiple-projects.sh` + +Logs in to STACKIT, iterates over a hard-coded list of project IDs, lists all SKE clusters in each project and writes a 60-day kubeconfig for every cluster into a single kubeconfig file. + +Edit the `projects=( ... )` array near the bottom of the script before running. + +### Flags + +- `-f, --filepath ` — destination kubeconfig path (default: `$HOME/.kube/config`). +- `-h, --help` — show usage. + +### Examples + +```bash +# Write to default ~/.kube/config (will prompt before overwriting) +./create-kubeconfig-multiple-projects.sh + +# Write to a custom location +./create-kubeconfig-multiple-projects.sh -f ~/.kube/stackit-config +``` + +--- + +## `delete-unused-volumes.sh` + +Lists all STACKIT volumes in the currently configured project and deletes those with status `AVAILABLE`. + +Set `DRY_RUN=1` at the top of the script to preview the deletions without executing them. + +### Examples + +```bash +# Make sure the right project is selected +stackit config set --project-id + +# Preview what would be deleted (edit DRY_RUN=1 in the script first) +./delete-unused-volumes.sh + +# Actually delete unused volumes +./delete-unused-volumes.sh +``` + +--- + +## `ske-show-versions.sh` + +Prints a tabular overview of SKE clusters across the project IDs configured in the `projectid` variable. For each nodepool it shows the Kubernetes version and the Flatcar machine image version, annotated with `supported` or the `expirationDate` for deprecated versions. Rows containing any deprecated version are highlighted in red. + +The script enforces a minimum STACKIT CLI version (`0.59.0`). + +Edit the `projectid="..."` variable (space-separated list) before running. + +### Example + +```bash +./ske-show-versions.sh +``` + +Sample output: + +``` +CLUSTER NAME SKE VERSION NODEPOOL FLATCAR VERSION MACHINE TYPE PROJECT ID +------------ ----------- -------- --------------- ------------ ---------- +prod-cluster 1.30.4 (supported) default 4081.2.0 (supported) c1.4 xxxxxxxx-... +legacy-cluster 1.27.9 (exp. 2026-05-01) default 3815.2.5 (exp. 2026-03-15) c1.2 yyyyyyyy-... + +Summary: +Total clusters: 2 +``` + +--- + +## `smctl.sh` + +Wrapper around the `vault` CLI to works with secrets in STACKIT Secrets Manager, think about it as `kubectl` but for secrets. + +### Required environment variables + +| Variable | Description | +| ------------- | -------------------------------------------------------------- | +| `SM_USERNAME` | STACKIT Secret Manager username | +| `SM_PASSWORD` | STACKIT Secret Manager password | +| `SM_ID` | KV secrets engine mount path (your secret manager instance ID) | + +### Commands + +| Command | Description | +| -------------------------------- | --------------------------------------------------------------------------------------------------- | +| `get ` | Print the value of a single key. | +| `get all` | Print all keys as `key: value`. | +| `get all-export` | Print all keys as `export key=value` (suitable for `eval`/`source`). | +| `put [value]` | Write/update a key. Reads from stdin if no value is given. Existing keys at the path are preserved. | +| `list` | List all secret paths under the mount. | +| `list ` | List all keys at a specific path. | +| `help` | Show usage. | + +### Examples + +```bash +export SM_USERNAME='my-user' +export SM_PASSWORD='my-pass' +export SM_ID='sm-xxxxxxxx' + +# List all paths +./smctl.sh list + +# List keys at a path +./smctl.sh list postgresql + +# Read a single value +./smctl.sh get postgresql db_password + +# Dump everything at a path +./smctl.sh get postgresql all + +# Load all secrets at a path into the current shell +eval "$(./smctl.sh get postgresql all-export)" + +# Write a value directly +./smctl.sh put postgresql db_password 'super-secret' + +# Write a value from a file (e.g. a full .env) +./smctl.sh put terraform secret-env < .env +``` + +--- + +## `list-project-resources.sh` + +Iterates over the listable STACKIT services (DNS, Git, Load Balancers, SKE etc.) and prints a Markdown report containing one section per project with a table per resource type. Intended to be redirected into a `.md` file. + +Project IDs are passed as arguments — at least one is required. + +### Example + +```bash +# Single project +./list-project-resources.sh xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx > project-inventory.md + +# Multiple projects in one report +./list-project-resources.sh \ + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ + yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy \ + > inventory.md +``` + +Sample output (excerpt): + +```markdown +## Project: my-prod-project (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + +**SKE Clusters:** +|name|status|...| +|---|---|---| +|prod-cluster|HEALTHY|...| + +**Public IPs:** +N/A + +last update: Thu, 16-Apr-2026 14:42:11 CEST +``` + +--- + +## `vault-migrate.sh` + +Migrates secrets between two KV v2-compatible secret backends, typically from a HashiCorp Vault instance into STACKIT Secrets Manager, or from one STACKIT Secrets Manager instance to another (both expose the Vault KV v2 API). Source authentication can be `userpass` or `ldap` (LDAP triggers an interactive password prompt); the target always uses `userpass`. Recursively walks all paths under the source mount unless `MIGRATE_PATHS` is set. + +### Flags + +- `-d, --dry-run` — show what would be migrated without writing. +- `-v, --verbose` — verbose progress output. +- `-s, --skip-existing` — skip secrets that already exist in the target. +- `--debug` — debug output (implies `--verbose`). +- `-h, --help` — show usage. + +### Required environment variables + +| Variable | Description | +| ---------------------- | ----------------------------------------------------------------- | +| `SOURCE_VAULT_ADDR` | Source Vault address (e.g. `https://vault.example.com`). | +| `SOURCE_SM_ID` | Source KV mount path. | +| `SOURCE_AUTH_METHOD` | `userpass` (default) or `ldap`. | +| `SOURCE_SM_USERNAME` | Source username — required when `SOURCE_AUTH_METHOD=userpass`. | +| `SOURCE_SM_PASSWORD` | Source password — required when `SOURCE_AUTH_METHOD=userpass`. | +| `SOURCE_LDAP_USERNAME` | LDAP username — required when `SOURCE_AUTH_METHOD=ldap`. | +| `TARGET_VAULT_ADDR` | Target Vault address. | +| `TARGET_SM_USERNAME` | Target Vault username (always userpass). | +| `TARGET_SM_PASSWORD` | Target Vault password. | +| `TARGET_SM_ID` | Target KV mount path. | +| `MIGRATE_PATHS` | Optional space-separated list of paths to migrate (default: all). | + +### Examples + +```bash +# Common target setup +export TARGET_VAULT_ADDR="https://prod.sm.eu01.stackit.cloud" +export TARGET_SM_USERNAME="target-user" +export TARGET_SM_PASSWORD='' +export TARGET_SM_ID="sm-target-xxxx" + +# 1) Dry run — migrate everything from a userpass source +export SOURCE_VAULT_ADDR="https://old.vault.example.com" +export SOURCE_SM_ID="sm-source-xxxx" +export SOURCE_AUTH_METHOD="userpass" +export SOURCE_SM_USERNAME="source-user" +export SOURCE_SM_PASSWORD='' +./vault-migrate.sh --dry-run --verbose + +# 2) Migrate from an LDAP source (will prompt for password) +export SOURCE_AUTH_METHOD="ldap" +export SOURCE_LDAP_USERNAME="myuser" +./vault-migrate.sh + +# 3) Migrate only selected paths, skipping ones that already exist +export MIGRATE_PATHS="postgresql redis terraform" +./vault-migrate.sh --skip-existing --verbose +``` diff --git a/scripts/check-stackit-ip.sh b/scripts/check-stackit-ip.sh new file mode 100755 index 0000000..48da5ff --- /dev/null +++ b/scripts/check-stackit-ip.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +# check-ip: Checks if a given IP address is in the STACKIT public IP ranges. +# Usage: ./check-ip +# Example: ./check-ip 45.129.40.1 + +# Check if an IP address was provided as an argument +if [ -z "$1" ]; then + echo "Usage: $0 " + echo "Example: $0 45.129.40.1" + exit 1 +fi + +IP_TO_CHECK="$1" + +# Check for required commands. +# We need 'stackit', 'jq', and 'grepcidr'. +for cmd in stackit jq grepcidr; do + if ! command -v $cmd &> /dev/null; then + echo "Error: Required command '$cmd' is not installed." >&2 + echo "Please install it and ensure it's in your PATH." >&2 + exit 1 + fi +done + +echo "Fetching STACKIT IP ranges..." >&2 + +FOUND=0 +RANGES_CHECKED=0 + +while read -r RANGE; do + # Skip empty lines, just in case + if [ -z "$RANGE" ]; then + continue + fi + + RANGES_CHECKED=$((RANGES_CHECKED + 1)) + + if echo "$IP_TO_CHECK" | grepcidr -s "$RANGE"; then + echo "Found: $IP_TO_CHECK is in the range $RANGE" + FOUND=1 + break + fi +done < <(stackit curl "https://iaas.api.eu01.stackit.cloud/v1/networks/public-ip-ranges" | jq -r '.items[].cidr') + +# Check if we processed any ranges at all +if [ $RANGES_CHECKED -eq 0 ]; then + echo "Error: Failed to fetch or parse IP ranges from STACKIT API." >&2 + exit 1 +fi + +# Check the flag after the loop +if [ $FOUND -eq 0 ]; then + # If the loop finished without finding anything, print a "not found" message + echo "Not found: $IP_TO_CHECK is not in any of the STACKIT ranges." + exit 1 +fi + +exit 0 diff --git a/scripts/list-project-resources.sh b/scripts/list-project-resources.sh new file mode 100755 index 0000000..02c3def --- /dev/null +++ b/scripts/list-project-resources.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +# Script to list (all) resources of a STACKIT project +# Ideally, you redirect the output into a markdown file. + +set -euo pipefail + +# Check if stackit cli is installed +if ! command -v stackit &> /dev/null; then + echo "Error: stackit command not found" + echo "Please install STACKIT CLI from:" + echo "https://github.com/stackitcloud/stackit-cli/blob/main/INSTALLATION.md" + exit 1 +fi + +# Check if stackit is properly authenticated; only login if the session is gone +if ! stackit auth get-access-token &> /dev/null; then + echo "Session expired. Logging in..." + stackit auth login || { echo "Error: stackit authentication failed."; exit 1; } +fi + +# Check if jq is installed +if ! command -v jq &> /dev/null; then + echo "Error: 'jq' is not installed." + exit 1 +fi + +# Function to display usage and handle errors +display_usage() { + echo "Usage: $0 ..." + echo "Example: $0 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy >output.md" + exit 1 +} + +# Function to get project information +get_project_name() { + local project_id=$1 + + local project_name="" + project_name=$(stackit project describe --project-id "$project_id" --output-format json 2>/dev/null \ + | jq -r '.name // empty') + # If the CLI call fails or .name is missing, fall back to the raw ID + [[ -z "$project_name" ]] && project_name="$project_id" + echo "## Project: $project_name ($project_id)" + echo "" +} + +# Function to get resource information +fetch_resources() { + local service=$1 + local type=$2 + local label=$3 + local fields=${4:-} # comma-separated list of fields to keep; empty = all + + # Build the CLI command - omit the type if it's empty + if [[ -z "$type" ]]; then + json_output=$(stackit "$service" list --project-id "$project_id" --output-format json) + else + json_output=$(stackit "$service" "$type" list --project-id "$project_id" --output-format json) + fi + + local header + local separation_line + local data + + # Check whether json output is empty/null - indicating no resource of that service type found - and print it + if [[ -z "$json_output" ]] || [[ "$json_output" == "[]" ]] || [[ "$json_output" == "null" ]]; then + echo "**$label:**" + echo "N/A" + echo -e "\n" + return + fi + + # Narrow output to only the requested fields (preserving their order) + if [[ -n "$fields" ]]; then + json_output=$(jq --arg fields "$fields" ' + ($fields | split(",")) as $keep | + map(. as $item | reduce $keep[] as $k ({}; . + {($k): $item[$k]})) + ' <<< "$json_output") + fi + + # Format the json output into a markdown table + header=$(jq -r '.[0] | to_entries | map(.key) | join("\t")' <<< "$json_output" | sed 's/\t/|/g; s/^/|/; s/$/|/') + separation_line=$(jq -r '.[0] | to_entries | map("|---") | join("")' <<< "$json_output") + data=$(jq -r '.[] | to_entries | map(.value|tostring) | join("\t")' <<< "$json_output" | sed 's/\t/|/g; s/^/|/; s/$/|/') + + echo "**$label:**" + echo "$header" + echo "$separation_line" + echo "$data" + echo -e "\n" +} + +get_project_resources() { + + # call function to get project name + get_project_name "$project_id" + + # Format: service, type, label, fields (comma-separated; empty = all fields) + services=("dns" "zone" "DNS Zones" "name,dnsName,type,visibility,state,recordCount,id" + "git" "instance" "Git Instances" "" + "load-balancer" "" "Load Balancers" "" + "logme" "instance" "LogMe Instances" "" + "mariadb" "instance" "MariaDB Instances" "" + "mongodbflex" "instance" "MongoDB Flex Instances" "" + "object-storage" "bucket" "Object Storage Buckets" "" + "observability" "instance" "Observability Instances" "" + "opensearch" "instance" "OpenSearch Instances" "" + "postgresflex" "instance" "Postgres Flex Instances" "" + "public-ip" "" "Public IPs" "" + "rabbitmq" "instance" "RabbitMQ Instances" "" + "redis" "instance" "Redis Instances" "" + "secrets-manager" "instance" "Secrets Manager Instances" "" + "service-account" "" "Service Accounts" "" + "ske" "cluster" "SKE Clusters" "") + + for ((i=0; i<${#services[@]}; i+=4)); do + fetch_resources "${services[i]}" "${services[i+1]}" "${services[i+2]}" "${services[i+3]}" + done + + echo "last update: $(date +"%a, %d-%b-%Y %H:%M:%S %Z")" +} + +# Main script +if [ $# -eq 0 ]; then + echo "Error: No project IDs provided" + display_usage +fi + +# Process each project ID +for project_id in "$@"; do + get_project_resources "$project_id" +done diff --git a/scripts/s3-ssec.sh b/scripts/s3-ssec.sh new file mode 100755 index 0000000..e60b534 --- /dev/null +++ b/scripts/s3-ssec.sh @@ -0,0 +1,74 @@ +#!/bin/bash +set -e # Exit immediately if a command exits with a non-zero status + +# Help / Usage output +usage() { + echo "Usage: $0 " + echo "Examples:" + echo " $0 upload /path/to/local/file.txt" + echo " $0 download file.txt" + exit 1 +} + +# Check for required arguments +if [ -z "$1" ] || [ -z "$2" ]; then + usage +fi + +ACTION="$1" +TARGET_FILE="$2" + +# ============================================================================== +# CONFIGURATION +# ============================================================================== +export AWS_ACCESS_KEY_ID="" +export AWS_SECRET_ACCESS_KEY="" +export AWS_DEFAULT_REGION="eu01" + +ENDPOINT="https://object.storage.eu01.onstackit.cloud" +BUCKET="s3://mybucket" +KEY_FILE="/root/ssec.key" + +# ============================================================================== +# 1. GENERATE BINARY KEY (Only required once if the key file doesn't exist) +# ============================================================================== +if [ ! -f "$KEY_FILE" ]; then + echo "[*] Generating new binary 32-byte AES key..." + openssl rand 32 > "$KEY_FILE" + chmod 600 "$KEY_FILE" +fi + +# ============================================================================== +# 2. ACTION EXECUTION (UPLOAD OR DOWNLOAD) +# ============================================================================== +if [ "$ACTION" == "upload" ]; then + # Check if the local file exists before uploading + if [ ! -f "$TARGET_FILE" ]; then + echo "Error: Local file '$TARGET_FILE' does not exist!" + exit 1 + fi + + REMOTE_NAME=$(basename "$TARGET_FILE") + + echo "[*] Starting SSE-C upload of '$TARGET_FILE' to STACKIT..." + aws --endpoint-url "$ENDPOINT" s3 cp "$TARGET_FILE" "$BUCKET/$REMOTE_NAME" \ + --sse-c AES256 \ + --sse-c-key "fileb://$KEY_FILE" + + echo "[+] Upload successful! '$REMOTE_NAME' is now stored encrypted in the bucket. 🚀" + +elif [ "$ACTION" == "download" ]; then + REMOTE_NAME=$(basename "$TARGET_FILE") + LOCAL_DOWNLOAD_PATH="./${REMOTE_NAME}_downloaded" + + echo "[*] Starting SSE-C download of '$REMOTE_NAME' from STACKIT..." + aws --endpoint-url "$ENDPOINT" s3 cp "$BUCKET/$REMOTE_NAME" "$LOCAL_DOWNLOAD_PATH" \ + --sse-c AES256 \ + --sse-c-key "fileb://$KEY_FILE" + + echo "[+] Download successful! Saved to '$LOCAL_DOWNLOAD_PATH' 🚀" + +else + echo "Error: Invalid action '$ACTION'." + usage +fi diff --git a/scripts/ske-show-versions.sh b/scripts/ske-show-versions.sh new file mode 100755 index 0000000..3107df0 --- /dev/null +++ b/scripts/ske-show-versions.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +export STACKIT_CLI_MIN_MAJOR=0 +export STACKIT_CLI_MIN_MINOR=59 +export STACKIT_CLI_MIN_PATCH=0 + +# Script to show SKE cluster versions and nodepool flatcar versions + +set -euo pipefail + +# Check if stackit command is available +if ! command -v stackit &> /dev/null; then + echo "Error: stackit command not found" + echo "Please install STACKIT CLI from:" + echo "https://github.com/stackitcloud/stackit-cli/blob/main/INSTALLATION.md" + exit 1 +fi + +# Check if stackit-cli is supported +stackit -v |awk -v maj="$STACKIT_CLI_MIN_MAJOR" -v min="$STACKIT_CLI_MIN_MINOR" -v pat="$STACKIT_CLI_MIN_PATCH" '/Version:/ { { split($2, a, "."); +current_maj = a[1]; +current_min = a[2]; +current_pat = a[3]; +if (current_maj > maj || (current_maj == maj && current_min > min) || (current_maj == maj && current_min == min && current_pat >= pat)) { + print "✅ Version supported (" current_maj "." current_min "." current_pat ")"; + exit 0; + } else { + print "❌ STACKIT CLI version not supported. Need at least " maj "." min "." pat; + exit 1; + } + } +}' + +# Define project IDs (space-separated) +projectid="xxxxxx yyyyy" + +# Collect all clusters from all projects +all_clusters="[]" + +echo "Fetching SKE cluster information..." +for pid in $projectid; do + echo "Fetching clusters for project ID: $pid" + clusters=$(stackit ske cluster list -o json --project-id "$pid") + + # Merge with existing clusters + all_clusters=$(echo "$all_clusters" "$clusters" | jq -s '.[0] + .[1]') +done + +# Print header +printf "\n%-20s %-30s %-20s %-30s %-15s %-40s\n" "CLUSTER NAME" "SKE VERSION" "NODEPOOL" "FLATCAR VERSION" "MACHINE TYPE" "PROJECT ID" +printf "%-20s %-30s %-20s %-30s %-15s %-40s\n" "------------" "-----------" "--------" "---------------" "------------" "----------" + +# Parse JSON and display information +for pid in $projectid; do + clusters=$(stackit ske cluster list -o json --project-id "$pid") + + echo "$clusters" | jq -r --arg pid "$pid" '.[] | + .name as $cluster_name | + .kubernetes.version as $k8s_version | + .nodepools[] | + [$cluster_name, $k8s_version, .name, .machine.image.version, .machine.type, $pid] | + @tsv' | while IFS=$'\t' read -r cluster ske_version nodepool flatcar_version machine_type project; do + + k8s_version_desc=$(stackit ske options kubernetes-versions -o json | jq -r --arg VERSION $ske_version '.kubernetesVersions[] | select(.version == $VERSION) | if .state == "deprecated" then "exp. "+.expirationDate[0:10] else "supported" end ') + k8s_version_state=$(stackit ske options kubernetes-versions -o json | jq -r --arg VERSION $ske_version '.kubernetesVersions[] | select(.version == $VERSION).state') + + flatcar_version_desc=$(stackit ske options machine-images -o json | jq -r --arg VERSION $flatcar_version '.machineImages[] | select(.name == "flatcar") | .versions[] | select(.version == $VERSION) | if .state == "deprecated" then "exp. "+.expirationDate[0:10] else "supported" end ' ) + flatcar_version_state=$(stackit ske options machine-images -o json | jq -r --arg VERSION $flatcar_version '.machineImages[] | select(.name == "flatcar") | .versions[] | select(.version == $VERSION).state' ) + + GREEN='\033[0;32m' + RED='\033[0;31m' + NC='\033[0m' + + if [[ "$k8s_version_state" == "deprecated" || "$flatcar_version_state" == "deprecated" ]]; then + ROW_COLOR=$RED + else + ROW_COLOR=$GREEN + fi + + printf "${ROW_COLOR}%-20s %-30s %-20s %-30s %-15s %-40s${NC}\n" "$cluster" "$ske_version ($k8s_version_desc)" "$nodepool" "$flatcar_version ($flatcar_version_desc) " "$machine_type" "$project" + done +done + +echo "" +echo "Summary:" +echo "$all_clusters" | jq -r 'length | "Total clusters: \(.)"' diff --git a/scripts/smctl.sh b/scripts/smctl.sh new file mode 100755 index 0000000..86504a8 --- /dev/null +++ b/scripts/smctl.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# Unified Secret Manager utility for STACKIT Secret Manager (Vault v2) +# Usage: smkey.sh [args] + +set -euo pipefail + +# Check for required dependencies +for cmd in vault jq; do + if ! command -v "$cmd" &> /dev/null; then + echo "Error: Required command '$cmd' is not installed or not in your PATH." >&2 + echo "Please install it before running this script." >&2 + exit 1 + fi +done + +export VAULT_ADDR="https://prod.sm.eu01.stackit.cloud" + +# Ensure required env variables are set +: "${SM_USERNAME:?Environment variable SM_USERNAME is not set}" +: "${SM_PASSWORD:?Environment variable SM_PASSWORD is not set}" +: "${SM_ID:?Environment variable SM_ID is not set}" + +# Function to authenticate and get Vault token +authenticate() { + if [ -z "${VAULT_TOKEN:-}" ]; then + export VAULT_TOKEN=$(vault login --address "${VAULT_ADDR}" -no-store -format=json \ + --method=userpass username="${SM_USERNAME}" password="${SM_PASSWORD}" 2>/dev/null | jq -r .auth.client_token) + fi +} + +# Function to display usage +usage() { + cat < [args] + +Commands: + get Get a specific secret value + Example: $0 get postgresql db_password + + get all Get all key-value pairs in "key: value" format + Example: $0 get postgresql all + + get all-export Get all key-value pairs in "export key=value" format + Example: $0 get postgresql all-export + + put [value] Put a secret value (from argument or stdin) + Example: $0 put postgresql db_password myvalue123 + Example: $0 put terraform secret-env < .env + + list [vault_path] List all available vault paths (no arg) + or list all keys in a specific vault path + Example: $0 list + Example: $0 list postgresql + + help Show this help message + +Environment variables required: + SM_USERNAME STACKIT Secret Manager username + SM_PASSWORD STACKIT Secret Manager password + SM_ID KV secrets engine mount path +EOF + exit 1 +} + +# Command: get +cmd_get() { + if [ $# -ne 2 ]; then + echo "Error: 'get' requires and arguments" + echo "Usage: $0 get " + echo " $0 get all # Get all keys in 'key: value' format" + echo " $0 get all-export # Get all keys in 'export key=value' format" + exit 1 + fi + + local vault_path="$1" + local secret_key="$2" + + authenticate + + # Handle special "all" and "all-export" keys + if [ "${secret_key}" = "all" ]; then + # Get all key-value pairs in "key: value" format + local secret_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null) + + if [ $? -ne 0 ] || [ -z "${secret_data}" ]; then + echo "Error: No secret found at path '${vault_path}'" + exit 1 + fi + + # Output in simple "key: value" format + echo "${secret_data}" | jq -r '.data.data | to_entries[] | "\(.key): \(.value)"' + return + fi + + if [ "${secret_key}" = "all-export" ]; then + # Get all key-value pairs in "export key=value" format + local secret_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null) + + if [ $? -ne 0 ] || [ -z "${secret_data}" ]; then + echo "Error: No secret found at path '${vault_path}'" + exit 1 + fi + + # Output in "export key=value" format + echo "${secret_data}" | jq -r '.data.data | to_entries[] | "export \(.key)=\(.value)"' + return + fi + + # Standard single key retrieval + vault kv get -mount="${SM_ID}" -field="${secret_key}" "${vault_path}" +} + +# Command: put +cmd_put() { + if [ $# -lt 2 ] || [ $# -gt 3 ]; then + echo "Error: 'put' requires and arguments, with optional " + echo "Usage: $0 put # Provide value directly" + echo " $0 put < input_file # Read value from file" + exit 1 + fi + + local vault_path="$1" + local secret_key="$2" + + authenticate + + # Get current secret data (if exists) + local current_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null | jq -r '.data.data' || echo "{}") + + # Read new value from argument or stdin + local new_value + if [ $# -eq 3 ]; then + # Value provided as argument + new_value="$3" + else + # Read value from stdin + new_value=$(cat) + fi + + # Merge existing keys with new key/value + local updated_data=$(echo "${current_data}" | jq --arg k "${secret_key}" --arg v "${new_value}" '. + {($k): $v}') + + # Build arguments for vault kv put + local put_args=() + while IFS= read -r key; do + local value=$(echo "${updated_data}" | jq -r --arg k "$key" '.[$k]') + put_args+=("${key}=${value}") + done < <(echo "${updated_data}" | jq -r 'keys[]') + + # Write merged data back to Vault + vault kv put -mount="${SM_ID}" "${vault_path}" "${put_args[@]}" +} + +# Command: list +cmd_list() { + authenticate + + # If no path provided, list all vault paths + if [ $# -eq 0 ]; then + echo "Available vault paths:" + vault kv list -mount="${SM_ID}" -format=json | jq -r '.[]' 2>/dev/null || { + echo "Error: Unable to list vault paths or no paths found" + exit 1 + } + return + fi + + # If path provided, list all keys in that path + if [ $# -ne 1 ]; then + echo "Error: 'list' requires zero or one argument" + echo "Usage: $0 list [vault_path]" + exit 1 + fi + + local vault_path="$1" + + # Get the secret and list all keys + local secret_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null) + + if [ $? -ne 0 ] || [ -z "${secret_data}" ]; then + echo "Error: No secret found at path '${vault_path}'" + exit 1 + fi + + echo "Keys in ${vault_path}:" + echo "${secret_data}" | jq -r '.data.data | keys[]' +} + +# Main command dispatcher +if [ $# -eq 0 ]; then + usage +fi + +COMMAND="$1" +shift + +case "${COMMAND}" in + get) + cmd_get "$@" + ;; + put) + cmd_put "$@" + ;; + list) + cmd_list "$@" + ;; + help|--help|-h) + usage + ;; + *) + echo "Error: Unknown command '${COMMAND}'" + echo + usage + ;; +esac diff --git a/scripts/vault-migrate.sh b/scripts/vault-migrate.sh new file mode 100755 index 0000000..34c9dd9 --- /dev/null +++ b/scripts/vault-migrate.sh @@ -0,0 +1,467 @@ +#!/usr/bin/env bash +# Vault Secret Migration Script - Migrate secrets between Vault instances using v2 API +# Usage: vault-migrate.sh [options] + +set -euo pipefail + +# Default values +DRY_RUN=false +VERBOSE=false +SKIP_EXISTING=false +DEBUG=false + +# Source Vault configuration +: "${SOURCE_VAULT_ADDR:?Environment variable SOURCE_VAULT_ADDR is not set}" +: "${SOURCE_SM_ID:?Environment variable SOURCE_SM_ID is not set (KV mount path)}" + +# Source authentication method (userpass or ldap) +SOURCE_AUTH_METHOD="${SOURCE_AUTH_METHOD:-userpass}" + +# Validate source authentication variables based on method +if [ "${SOURCE_AUTH_METHOD}" = "userpass" ]; then + : "${SOURCE_SM_USERNAME:?Environment variable SOURCE_SM_USERNAME is not set (required for userpass auth)}" + : "${SOURCE_SM_PASSWORD:?Environment variable SOURCE_SM_PASSWORD is not set (required for userpass auth)}" +elif [ "${SOURCE_AUTH_METHOD}" = "ldap" ]; then + : "${SOURCE_LDAP_USERNAME:?Environment variable SOURCE_LDAP_USERNAME is not set (required for ldap auth)}" + # Note: LDAP password is NOT required as env var - will use interactive login +else + echo "Error: SOURCE_AUTH_METHOD must be 'userpass' or 'ldap' (got: ${SOURCE_AUTH_METHOD})" + exit 1 +fi + +# Target Vault configuration +: "${TARGET_VAULT_ADDR:?Environment variable TARGET_VAULT_ADDR is not set}" +: "${TARGET_SM_USERNAME:?Environment variable TARGET_SM_USERNAME is not set}" +: "${TARGET_SM_PASSWORD:?Environment variable TARGET_SM_PASSWORD is not set}" +: "${TARGET_SM_ID:?Environment variable TARGET_SM_ID is not set (KV mount path)}" + +# Optional: specify paths to migrate (if not set, migrate all) +MIGRATE_PATHS="${MIGRATE_PATHS:-}" + +# Check for required dependencies +for cmd in vault jq; do + if ! command -v "$cmd" &>/dev/null; then + echo "Error: Required command '$cmd' not found in PATH" + exit 1 + fi +done + +# Function to display usage +usage() { + cat </dev/null | jq -r .auth.client_token) + elif [ "${SOURCE_AUTH_METHOD}" = "ldap" ]; then + log_info "Authenticating to source Vault at ${SOURCE_VAULT_ADDR} (method: ldap, user: ${SOURCE_LDAP_USERNAME})..." + + # Interactive LDAP login - vault will prompt for password + if ! vault login -address="${SOURCE_VAULT_ADDR}" -method=ldap username="${SOURCE_LDAP_USERNAME}" >/dev/null 2>&1; then + echo "Error: Failed to authenticate to source Vault using LDAP" + exit 1 + fi + + # Read token from vault token file + if [ -f "${HOME}/.vault-token" ]; then + SOURCE_VAULT_TOKEN=$(cat "${HOME}/.vault-token") + else + echo "Error: Vault token file not found at ${HOME}/.vault-token" + exit 1 + fi + fi + + if [ -z "${SOURCE_VAULT_TOKEN}" ] || [ "${SOURCE_VAULT_TOKEN}" = "null" ]; then + echo "Error: Failed to authenticate to source Vault using ${SOURCE_AUTH_METHOD}" + exit 1 + fi + log_verbose "✓ Source authentication successful" + fi +} + +# Function to authenticate to target Vault +authenticate_target() { + if [ -z "${TARGET_VAULT_TOKEN:-}" ]; then + log_verbose "Authenticating to target Vault at ${TARGET_VAULT_ADDR}..." + TARGET_VAULT_TOKEN=$(vault login --address "${TARGET_VAULT_ADDR}" -no-store -format=json \ + --method=userpass username="${TARGET_SM_USERNAME}" password="${TARGET_SM_PASSWORD}" 2>/dev/null | jq -r .auth.client_token) + + if [ -z "${TARGET_VAULT_TOKEN}" ] || [ "${TARGET_VAULT_TOKEN}" = "null" ]; then + echo "Error: Failed to authenticate to target Vault" + exit 1 + fi + log_verbose "✓ Target authentication successful" + fi +} + +# Logging functions +log_verbose() { + if [ "${VERBOSE}" = true ]; then + echo "$@" >&2 + fi +} + +log_debug() { + if [ "${DEBUG}" = true ]; then + echo "[DEBUG] $@" >&2 + fi +} + +log_info() { + echo "$@" >&2 +} + +log_error() { + echo "ERROR: $@" >&2 +} + +# Function to list paths at a given location +list_paths_at() { + local base_path="$1" + local error_output + error_output=$(mktemp) + + local paths + # Try JSON format first + paths=$(VAULT_ADDR="${SOURCE_VAULT_ADDR}" VAULT_TOKEN="${SOURCE_VAULT_TOKEN}" \ + vault kv list -mount="${SOURCE_SM_ID}" "${base_path}" -format=json 2>"${error_output}" | jq -r '.[]' 2>/dev/null || echo "") + + # If JSON failed, try table format + if [ -z "${paths}" ]; then + paths=$(VAULT_ADDR="${SOURCE_VAULT_ADDR}" VAULT_TOKEN="${SOURCE_VAULT_TOKEN}" \ + vault kv list -mount="${SOURCE_SM_ID}" "${base_path}" 2>"${error_output}" | grep -v "^Keys$" | grep -v "^----$" | grep -v "^$" || echo "") + fi + + rm -f "${error_output}" + echo "${paths}" +} + +# Function to recursively get all secret paths from source Vault +get_all_paths_recursive() { + local prefix="$1" + local paths + + paths=$(list_paths_at "${prefix}") + + for item in ${paths}; do + if [[ "${item}" == */ ]]; then + # It's a folder, recurse into it + log_debug "Found folder: ${prefix}${item}" + get_all_paths_recursive "${prefix}${item}" + else + # It's a secret, add it to the list + echo "${prefix}${item}" + fi + done +} + +# Function to get all paths from source Vault +get_all_paths() { + authenticate_source + + log_verbose "Fetching all secret paths from source Vault..." + + local cmd="VAULT_ADDR=\"${SOURCE_VAULT_ADDR}\" VAULT_TOKEN=\"${SOURCE_VAULT_TOKEN}\" vault kv list -mount=\"${SOURCE_SM_ID}\"" + log_debug "Command: ${cmd}" + + # Get all paths recursively starting from root + local all_secrets + all_secrets=$(get_all_paths_recursive "") + + if [ -z "${all_secrets}" ]; then + log_error "No secrets found in source Vault" + log_error "Mount path: ${SOURCE_SM_ID}" + exit 1 + fi + + log_debug "Found secrets: ${all_secrets}" + echo "${all_secrets}" +} + +# Function to get secret from source +get_source_secret() { + local path="$1" + + authenticate_source + + local cmd="VAULT_ADDR=\"${SOURCE_VAULT_ADDR}\" VAULT_TOKEN=\"\" vault kv get -mount=\"${SOURCE_SM_ID}\" -format=json \"${path}\"" + log_debug "Command: ${cmd}" + + local error_output + error_output=$(mktemp) + + local result + result=$(VAULT_ADDR="${SOURCE_VAULT_ADDR}" VAULT_TOKEN="${SOURCE_VAULT_TOKEN}" \ + vault kv get -mount="${SOURCE_SM_ID}" -format=json "${path}" 2>"${error_output}" | jq -r '.data.data') + + local exit_code=$? + + if [ ${exit_code} -ne 0 ] || [ -z "${result}" ] || [ "${result}" = "null" ]; then + log_debug "Failed to read secret at path: ${path}" + if [ -s "${error_output}" ]; then + log_debug "Vault error output:" + cat "${error_output}" >&2 + fi + rm -f "${error_output}" + echo "" + return 1 + fi + + rm -f "${error_output}" + echo "${result}" +} + +# Function to check if secret exists in target +target_secret_exists() { + local path="$1" + + authenticate_target + + VAULT_ADDR="${TARGET_VAULT_ADDR}" VAULT_TOKEN="${TARGET_VAULT_TOKEN}" \ + vault kv get -mount="${TARGET_SM_ID}" -format=json "${path}" 2>/dev/null >/dev/null + + return $? +} + +# Function to put secret to target +put_target_secret() { + local path="$1" + local secret_data="$2" + + authenticate_target + + # Convert JSON object to key=value arguments + local put_args=() + while IFS= read -r key; do + local value=$(echo "${secret_data}" | jq -r --arg k "$key" '.[$k]') + put_args+=("${key}=${value}") + done < <(echo "${secret_data}" | jq -r 'keys[]') + + if [ ${#put_args[@]} -eq 0 ]; then + log_error "No key-value pairs found in secret data for path '${path}'" + return 1 + fi + + VAULT_ADDR="${TARGET_VAULT_ADDR}" VAULT_TOKEN="${TARGET_VAULT_TOKEN}" \ + vault kv put -mount="${TARGET_SM_ID}" "${path}" "${put_args[@]}" >/dev/null 2>&1 +} + +# Function to migrate a single secret path +migrate_secret() { + local path="$1" + local migrated_count=0 + local skipped_count=0 + local failed_count=0 + + log_info "Processing: ${path}" + + # Check if secret exists in target and skip if requested + if [ "${SKIP_EXISTING}" = true ]; then + if target_secret_exists "${path}"; then + log_info " ⊘ Skipped (already exists in target)" + echo "0:1:0" + return 0 + fi + fi + + # Get secret from source + local secret_data + secret_data=$(get_source_secret "${path}") + + if [ -z "${secret_data}" ] || [ "${secret_data}" = "null" ]; then + log_info " ⊘ Skipped (empty or deleted secret)" + log_verbose " Path: ${path}" + echo "0:1:0" + return 0 + fi + + # Count keys + local key_count + key_count=$(echo "${secret_data}" | jq 'keys | length') + log_verbose " Found ${key_count} keys" + + # Dry run mode + if [ "${DRY_RUN}" = true ]; then + log_info " → Would migrate ${key_count} keys (dry-run)" + if [ "${VERBOSE}" = true ]; then + echo "${secret_data}" | jq -r 'to_entries[] | " - \(.key)"' >&2 + fi + echo "0:0:0" + return 0 + fi + + # Put secret to target + if put_target_secret "${path}" "${secret_data}"; then + log_info " ✓ Migrated ${key_count} keys" + echo "1:0:0" + else + log_error " ✗ Failed to write to target" + echo "0:0:1" + fi +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -d|--dry-run) + DRY_RUN=true + shift + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -s|--skip-existing) + SKIP_EXISTING=true + shift + ;; + --debug) + DEBUG=true + VERBOSE=true # Debug implies verbose + shift + ;; + -h|--help) + usage + ;; + *) + echo "Error: Unknown option: $1" + echo + usage + ;; + esac +done + +# Main migration logic +main() { + log_info "=== Vault Secret Migration ===" + log_info "Source: ${SOURCE_VAULT_ADDR} (mount: ${SOURCE_SM_ID}, auth: ${SOURCE_AUTH_METHOD})" + log_info "Target: ${TARGET_VAULT_ADDR} (mount: ${TARGET_SM_ID}, auth: userpass)" + + if [ "${DRY_RUN}" = true ]; then + log_info "Mode: DRY RUN (no changes will be made)" + fi + + log_info "" + + # Authenticate to both vaults + authenticate_source + authenticate_target + + # Determine which paths to migrate + local paths_to_migrate + if [ -n "${MIGRATE_PATHS}" ]; then + log_info "Migrating specified paths: ${MIGRATE_PATHS}" + paths_to_migrate="${MIGRATE_PATHS}" + else + log_info "Migrating all paths from source Vault..." + paths_to_migrate=$(get_all_paths) + fi + + log_info "" + + # Statistics + local total_migrated=0 + local total_skipped=0 + local total_failed=0 + local total_paths=0 + + # Migrate each path + for path in ${paths_to_migrate}; do + ((total_paths++)) || true + + result=$(migrate_secret "${path}") + IFS=':' read -r migrated skipped failed <<< "${result}" + + ((total_migrated+=migrated)) || true + ((total_skipped+=skipped)) || true + ((total_failed+=failed)) || true + done + + # Print summary + log_info "" + log_info "=== Migration Summary ===" + log_info "Total paths processed: ${total_paths}" + + if [ "${DRY_RUN}" = false ]; then + log_info "Successfully migrated: ${total_migrated}" + if [ "${SKIP_EXISTING}" = true ]; then + log_info "Skipped (existing): ${total_skipped}" + fi + log_info "Failed: ${total_failed}" + + if [ ${total_failed} -gt 0 ]; then + log_error "Migration completed with errors" + exit 1 + else + log_info "" + log_info "✓ Migration completed successfully!" + fi + else + log_info "✓ Dry run completed" + fi +} + +# Run main function +main