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/CONTRIBUTING.md b/CONTRIBUTING.md index 32c9f67..167ea7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ Your contribution is welcome! Thank you for your interest in growing our shared ### Pre-Commit Checks & CI -To keep our codebase clean and secure, we enforce a strict CI pipeline on all Pull Requests. You can save time by running these checks locally before you commit: +To maintain a clean and secure codebase, we enforce a strict CI pipeline on all Pull Requests. You can save time and catch pipeline failures early by running these checks locally before you commit your code. We use pre-commit to automate this process. - **Format your code:** The pipeline will fail if your code is not formatted according to industry standards. - Terraform: `terraform fmt -recursive` 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 69041b0..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 @@ -39,3 +45,4 @@ Whenever you are starting a new project, looking for a specific implementation, Did you find a script that is broken because of a recent update? Did you build a new, awesome example that could help your colleagues? Since this is a best-effort repository, we highly encourage you to open a Pull Request and share your fixes or additions! We all benefit from a growing, shared knowledge base. Please refer to our `CONTRIBUTING.md` for guidelines on how to format your code before submitting. +Because this repository is maintained on a best-effort basis, discussions and reviews may take some time. However, we strive to respond within 7 business days. 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-ha-vrrp/03-master.tf b/examples/iaas-ha-vrrp/03-master.tf index 81b10ee..189b6af 100644 --- a/examples/iaas-ha-vrrp/03-master.tf +++ b/examples/iaas-ha-vrrp/03-master.tf @@ -22,7 +22,7 @@ resource "stackit_server" "example01" { performance_class = "storage_premium_perf6" delete_on_termination = true } - machine_type = "c1.4" + machine_type = "c2i.4" availability_zone = "eu01-1" user_data = local.user_data_master keypair_name = stackit_key_pair.admin_keypair.name diff --git a/examples/iaas-ha-vrrp/04-backup.tf b/examples/iaas-ha-vrrp/04-backup.tf index 4aae376..21fb655 100644 --- a/examples/iaas-ha-vrrp/04-backup.tf +++ b/examples/iaas-ha-vrrp/04-backup.tf @@ -22,7 +22,7 @@ resource "stackit_server" "example02" { performance_class = "storage_premium_perf6" delete_on_termination = true } - machine_type = "c1.4" + machine_type = "c2i.4" availability_zone = "eu01-2" user_data = local.user_data_backup keypair_name = stackit_key_pair.admin_keypair.name diff --git a/examples/iaas-ha-vrrp/STACKIT-CLI-GUIDE.md b/examples/iaas-ha-vrrp/STACKIT-CLI-GUIDE.md index ee4435a..01b09cc 100644 --- a/examples/iaas-ha-vrrp/STACKIT-CLI-GUIDE.md +++ b/examples/iaas-ha-vrrp/STACKIT-CLI-GUIDE.md @@ -97,7 +97,7 @@ stackit server create \ --boot-volume-source-id 03e19c6a-d73a-4ba9-96af-4bd03cf905d3 \ # Debian 12 image ID --keypair-name \ --availability-zone eu01-1 \ - --machine-type c1.2 \ + --machine-type c2i.4 \ --name \ --network-interface-ids $NICID ``` 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/iaas-windows-byol-stackit-migration/MAINTAINERS.md b/examples/iaas-windows-byol-stackit-migration/MAINTAINERS.md new file mode 100644 index 0000000..1d61768 --- /dev/null +++ b/examples/iaas-windows-byol-stackit-migration/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Gurwinder Singh (gurwinder.singh@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-windows-byol-stackit-migration/README.md b/examples/iaas-windows-byol-stackit-migration/README.md new file mode 100644 index 0000000..775a0a9 --- /dev/null +++ b/examples/iaas-windows-byol-stackit-migration/README.md @@ -0,0 +1,228 @@ +# **Guide: BYOL Migration to STACKIT** + +> ⚠️ Example images are still in German. Translating them into English is an open TODO. + +This document provides a migration path for your custom-built Windows Server VM (Bring Your Own License) from a local virtualization environment (e.g., Hyper-V / VirtualBox) to the STACKIT cloud platform. + +The detailed process ensures technical compatibility through the integration of VirtIO drivers and the conversion of disk images. Following these steps allows you to use your own Windows licenses within the STACKIT cloud. + +--- + +### **Prerequisites** + +To successfully complete this workflow, you need access to the following tools and resources: + +- **STACKIT Windows VM (Recommended Sizing)** + - Flavor G2i.8 + - Disk OS Perf6 - 64GB + - Data/Image Disk Perf10: 100GB +- **Hyper-V:** Install as a virtualization platform via the Windows Role/Feature (e.g., via Server Manager). +- **Qemu-img:** [https://www.qemu.org/download/#windows](https://www.qemu.org/download/#windows) +- **STACKIT CLI:** [https://github.com/stackitcloud/stackit-cli/releases](https://github.com/stackitcloud/stackit-cli/releases) +- **Virtio Drivers:** [https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/?C=M;O=D](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/?C=M;O=D) +- **Cloud-base Init:** [https://github.com/cloudbase/cloudbase-init/releases](https://github.com/cloudbase/cloudbase-init/releases) + +--- + +### **Step-by-Step Migration** + +1. **Set up a new VM:** Action (**Aktion**) → New (**Neu**) → Virtual Machine (**Virtueller Computer**) +

+ +2. Click **Next** (**Weiter**) +

+ +3. **Specify Name and Location:** Enter the name of the new VM and, if necessary, a different storage location → **Next** (**Weiter**) +

+ +4. Select **Generation 2** + + > **Note:** With Generation 2, you must manually press "Any Key" during startup to boot from the ISO image. If you miss this moment, the installation routine will not start! + +

+ +5. **Assign Memory:** Startup memory (**Arbeitsspeicher beim Start**) → Enter value as needed (e.g., 4096 MB). + **Uncheck** the box: Use Dynamic Memory for this virtual machine (**Dynamischen Arbeitsspeicher für diesen virtuellen Computer verwenden**) +

+ +6. **Configure Networking:** Connection (**Verbindung**) → Not Connected (**Nicht verbunden**) +

+ +7. **Connect Virtual Hard Disk:** Define Name, Location (**Pfad**), and Size (**Größe**) +

+ The configured size corresponds to the minimum volume size of the future server in STACKIT. + +8. **Installation Options:** Install an operating system from a bootable CD/DVD-ROM (**Betriebssystem von einer startbaren CD/DVD-ROM installieren**) → Select Image file (**Abbilddatei (ISO)**) and use Browse (**Durchsuchen**) to select the required ISO image. +

+ +9. **Finish the New Virtual Machine Wizard:** Click **Finish** (**Fertig stellen**) +

+ +10. **Hyper-V Manager** view after creating the new VM +

+ +11. **Attach the Virtio drivers via ISO:** +

+ +12. Click **Connect** (**Verbinden**) to the new VM +

+

+ +13. Start the new VM for the first time and perform the OS installation +

+ +14. **Perform Windows Server Setup** (Screenshots based on Windows Server 2022): +

+

+ +15.

+ +16.

+ +17.

+ +18. Use the **Load Driver** (**Treiber laden**) selection +

+ +19. Installation of **three** Virtio drivers is now required so the image can be used on the STACKIT Hypervisor: +

+ + **NetKVM Driver** +

+

+ + **Viostor** +

+

+ + **Vioscsi** +

+

+ +20.

+ +21.

+ +22.

+ +23. **Display Configuration** +

+ +24. The two Virtio packages (**virtio-win-gt-x64.msi** and **virtio-win-guest-tools.exe**) from the Virtio ISO file should now be installed. It is also recommended to copy the content of the Virtio ISO file to the new system (e.g., `C:\temp\virtio\`). This has the advantage of being able to reinstall drivers relatively easily later. + +25. **Delete the Windows Recovery Partition** + This step is mandatory so that the volume of the future server on STACKIT can be flexibly expanded. + +| Step | Command | Details / Notes | +| :---- | :-------------------------- | :---------------------------------------------------------------- | +| **1** | `diskpart` | Starts the partitioning program. | +| **2** | `select disk 0` | Selects the hard disk. **Be sure to check** if Disk 0 is correct! | +| **3** | `list partition` | Displays all existing partitions. | +| **4** | `select partition ` | Select the number of the Recovery partition. | +| **5** | `delete partition override` | Forces the deletion of the partition. | +| **6** | `list partition` | Check if the partition was successfully removed. | + +26. The Windows system can now be customized with individual software and prepared for the future image. + +27. Finally, run the [**Cloudbase-init Tool**](https://cloudbase.it/cloudbase-init/) on the Windows VM to bring Windows into the final starting position for the move to the STACKIT Cloud! + +28. Start **Cloudbase-Init Setup** +

+ +29. Agree to the **License Agreement** (**Lizenzvereinbarung**) +

+ +30. Confirm **Setup Type** +

+ +31. Define **Configuration Options** +

+ +32. Start **Installation** +

+ +33. Finish installation and execute **Sysprep** (**Sysprep ausführen**) +

+ +34. **Sysprep generalization** is running +

+ +--- + +### **35. Image-Upload & VM Creation in STACKIT** + +After the local preparation is complete, the image is converted and transferred via STACKIT CLI. + +#### **36. Image Conversion (qCow2)** + +Convert the local VHDX into qcow2 format: + +````bash +qemu-img convert -f vhdx -O qcow2 + +#### 37. STACKIT CLI Login +Authenticate at the CLI: + +```bash +stackit auth login +```` + +#### 38. Image Upload + +Upload the image to your STACKIT project: + +```bash +stackit image create --name --disk-format=qcow2 --local-file-path="" -p +``` + +#### 39. Status Check + +Check the upload progress and details: + +```bash +stackit image list -p +stackit image describe -p +``` + +> **Important:** Take the generated `imageID` from the output. You must specify this ID as `` in the next step to create the volume and the VM based on this image. + +#### 40. Provisioning (Volume & Server) + +First create the volume and then start the VM: + +**Step 1: Create Volume** + +```bash +stackit volume create --availability-zone \ +--name --source-id \ +--source-type image --size -p +``` + +**Step 2: Instantiate Server** + +```bash +stackit server create -n \ +--availability-zone --machine-type \ +--network-id --boot-volume-source-id \ +--boot-volume-source-type volume -p +``` + +#### 41. Image Sharing (Cross-Project) + +Share the image for other Project IDs within the organization: + +```bash +stackit curl -X PATCH -H "Content-Type: application/json" \ +--data '{"projects": ["", ""]}' \ +https://iaas.api.eu01.stackit.cloud/v1/projects//images//share +``` + +#### 42. Completion + +Check if all drivers are correctly loaded in the operating system. +After starting the VM in STACKIT, check the **Device Manager** (**Gerätemanager**) to verify that all drivers have been loaded properly. + +

+ +References: +[https://docs.stackit.cloud/stackit/en/create-a-windows-server-via-stackit-iaas-api-cli-98304598.html](https://docs.stackit.cloud/stackit/en/create-a-windows-server-via-stackit-iaas-api-cli-98304598.html) diff --git a/examples/iaas-windows-byol-stackit-migration/images/image1.jpg b/examples/iaas-windows-byol-stackit-migration/images/image1.jpg new file mode 100644 index 0000000..f5c92e9 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image1.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image10.jpg b/examples/iaas-windows-byol-stackit-migration/images/image10.jpg new file mode 100644 index 0000000..a55f83e Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image10.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image11.jpg b/examples/iaas-windows-byol-stackit-migration/images/image11.jpg new file mode 100644 index 0000000..4f55157 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image11.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image12.jpg b/examples/iaas-windows-byol-stackit-migration/images/image12.jpg new file mode 100644 index 0000000..357c20e Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image12.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image13.jpg b/examples/iaas-windows-byol-stackit-migration/images/image13.jpg new file mode 100644 index 0000000..8114e33 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image13.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image14.jpg b/examples/iaas-windows-byol-stackit-migration/images/image14.jpg new file mode 100644 index 0000000..6d6cdde Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image14.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image15.jpg b/examples/iaas-windows-byol-stackit-migration/images/image15.jpg new file mode 100644 index 0000000..1c9abf1 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image15.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image16.jpg b/examples/iaas-windows-byol-stackit-migration/images/image16.jpg new file mode 100644 index 0000000..e4b7de3 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image16.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image17.jpg b/examples/iaas-windows-byol-stackit-migration/images/image17.jpg new file mode 100644 index 0000000..b1b2a95 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image17.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image18.jpg b/examples/iaas-windows-byol-stackit-migration/images/image18.jpg new file mode 100644 index 0000000..6226618 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image18.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image19.jpg b/examples/iaas-windows-byol-stackit-migration/images/image19.jpg new file mode 100644 index 0000000..ffcae3a Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image19.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image2.jpg b/examples/iaas-windows-byol-stackit-migration/images/image2.jpg new file mode 100644 index 0000000..6064840 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image2.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image20.jpg b/examples/iaas-windows-byol-stackit-migration/images/image20.jpg new file mode 100644 index 0000000..b26535e Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image20.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image21.jpg b/examples/iaas-windows-byol-stackit-migration/images/image21.jpg new file mode 100644 index 0000000..c249e0f Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image21.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image22.jpg b/examples/iaas-windows-byol-stackit-migration/images/image22.jpg new file mode 100644 index 0000000..e268e7c Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image22.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image23.jpg b/examples/iaas-windows-byol-stackit-migration/images/image23.jpg new file mode 100644 index 0000000..d58d457 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image23.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image24.jpg b/examples/iaas-windows-byol-stackit-migration/images/image24.jpg new file mode 100644 index 0000000..f19c5ab Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image24.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image25.jpg b/examples/iaas-windows-byol-stackit-migration/images/image25.jpg new file mode 100644 index 0000000..cd18c4d Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image25.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image26.jpg b/examples/iaas-windows-byol-stackit-migration/images/image26.jpg new file mode 100644 index 0000000..a5c329b Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image26.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image27.jpg b/examples/iaas-windows-byol-stackit-migration/images/image27.jpg new file mode 100644 index 0000000..2b17255 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image27.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image28.jpg b/examples/iaas-windows-byol-stackit-migration/images/image28.jpg new file mode 100644 index 0000000..74e126c Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image28.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image29.jpg b/examples/iaas-windows-byol-stackit-migration/images/image29.jpg new file mode 100644 index 0000000..60f95f8 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image29.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image3.jpg b/examples/iaas-windows-byol-stackit-migration/images/image3.jpg new file mode 100644 index 0000000..f68b36e Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image3.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image30.jpg b/examples/iaas-windows-byol-stackit-migration/images/image30.jpg new file mode 100644 index 0000000..940c99c Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image30.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image31.jpg b/examples/iaas-windows-byol-stackit-migration/images/image31.jpg new file mode 100644 index 0000000..ecf2aa1 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image31.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image32.jpg b/examples/iaas-windows-byol-stackit-migration/images/image32.jpg new file mode 100644 index 0000000..150e4b8 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image32.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image33.jpg b/examples/iaas-windows-byol-stackit-migration/images/image33.jpg new file mode 100644 index 0000000..c1f22a8 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image33.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image34.jpg b/examples/iaas-windows-byol-stackit-migration/images/image34.jpg new file mode 100644 index 0000000..7814f28 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image34.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image35.jpg b/examples/iaas-windows-byol-stackit-migration/images/image35.jpg new file mode 100644 index 0000000..d69d0eb Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image35.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image36.jpg b/examples/iaas-windows-byol-stackit-migration/images/image36.jpg new file mode 100644 index 0000000..ef1c06f Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image36.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image37.jpg b/examples/iaas-windows-byol-stackit-migration/images/image37.jpg new file mode 100644 index 0000000..1a7c458 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image37.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image4.jpg b/examples/iaas-windows-byol-stackit-migration/images/image4.jpg new file mode 100644 index 0000000..f8eca42 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image4.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image41.jpg b/examples/iaas-windows-byol-stackit-migration/images/image41.jpg new file mode 100644 index 0000000..a186ff6 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image41.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image5.jpg b/examples/iaas-windows-byol-stackit-migration/images/image5.jpg new file mode 100644 index 0000000..f72cea3 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image5.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image6.jpg b/examples/iaas-windows-byol-stackit-migration/images/image6.jpg new file mode 100644 index 0000000..84eb667 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image6.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image7.jpg b/examples/iaas-windows-byol-stackit-migration/images/image7.jpg new file mode 100644 index 0000000..9c92639 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image7.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image8.jpg b/examples/iaas-windows-byol-stackit-migration/images/image8.jpg new file mode 100644 index 0000000..7e13ae6 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image8.jpg differ diff --git a/examples/iaas-windows-byol-stackit-migration/images/image9.jpg b/examples/iaas-windows-byol-stackit-migration/images/image9.jpg new file mode 100644 index 0000000..c798e49 Binary files /dev/null and b/examples/iaas-windows-byol-stackit-migration/images/image9.jpg differ 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. + +--- + +## SKE Integration + +Once the IdP integration for SKE is enabled, you can create an IdP-based kubeconfig. Using this kubeconfig to interact with the Kubernetes API server will authenticate you via the STACKIT IdP, which is federated through your connected Authentik instance. + +For more details, see the [official STACKIT documentation](https://docs.stackit.cloud/products/runtime/kubernetes-engine/getting-started/access-cluster/#enable-idp-integration-in-cluster). + +### Step 1: Create IdP Kubeconfig + +Use the STACKIT CLI to create a kubeconfig that uses the IdP: + +```bash +stackit ske kubeconfig create --project-id --idp +``` + +### Step 2: Switch Context + +Switch your `kubectl` context to the newly created IdP context: + +```bash +kubectl config use-context +``` + +### Step 3: Verify Identity + +Verify your identity using `kubectl auth whoami`: + +```bash +kubectl auth whoami +``` + +![Terminal](docs/terminal-k-whoami.png) + +The Kubernetes API will return your username, UID, and groups. The Group ID should match the associated group from Authentik. You can confirm the group ID in the STACKIT Portal's Access Management UI: + +![SCIM-UI](docs/show-scim-group-id.png) + +## 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/iam-scim-integration/docs/show-scim-group-id.png b/examples/iam-scim-integration/docs/show-scim-group-id.png new file mode 100644 index 0000000..cdbbb53 Binary files /dev/null and b/examples/iam-scim-integration/docs/show-scim-group-id.png differ diff --git a/examples/iam-scim-integration/docs/terminal-k-whoami.png b/examples/iam-scim-integration/docs/terminal-k-whoami.png new file mode 100644 index 0000000..f474530 Binary files /dev/null and b/examples/iam-scim-integration/docs/terminal-k-whoami.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/README.md b/examples/ske-encrypted-volumes/README.md index 3fdfd40..34ed07a 100644 --- a/examples/ske-encrypted-volumes/README.md +++ b/examples/ske-encrypted-volumes/README.md @@ -1,5 +1,7 @@ # Encrypted Volumes for SKE +> ⚠️This example assumes that your project or organization has been enabled for a preview version of the STACKIT CSI Driver. If you wish to use encrypted volumes, please contact your account manager. + ## Overview This guide demonstrates how to roll out an encrypted storage class for SKE using the STACKIT Key Management Service (KMS). To achieve this, we use a **Service Account Impersonation** (Act-As) pattern. This allows the internal SKE service account to perform encryption and decryption tasks on behalf of a user-managed service account that has been granted access to your KMS keys. 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 acbc06c..f158042 100644 --- a/examples/ske-external-secrets-sync/050-random-secret.tf +++ b/examples/ske-external-secrets-sync/050-random-secret.tf @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -resource "random_password" "this" { +ephemeral "random_password" "this" { length = 16 special = true override_special = "!#$%&*()-_=+[]{}<>:?" @@ -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 = random_password.this.result + 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-nginx-rate-limit/03-ske.tf b/examples/ske-nginx-rate-limit/03-ske.tf index 5c55cdd..945010b 100644 --- a/examples/ske-nginx-rate-limit/03-ske.tf +++ b/examples/ske-nginx-rate-limit/03-ske.tf @@ -26,7 +26,7 @@ resource "stackit_ske_cluster" "default" { node_pools = [ { name = "standard" - machine_type = "c1.4" + machine_type = "c2i.4" minimum = "1" maximum = "3" max_surge = "3" 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 806856a..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" @@ -40,7 +19,7 @@ resource "stackit_ske_cluster" "example" { node_pools = [ { name = "standard" - machine_type = "c1.4" + machine_type = "c2i.4" minimum = "3" maximum = "9" max_surge = "3" 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 1261cf4..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" @@ -40,7 +19,7 @@ resource "stackit_ske_cluster" "example" { node_pools = [ { name = "standard" - machine_type = "c1.4" + machine_type = "c2i.4" minimum = "3" maximum = "9" max_surge = "3" 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/create-kubeconfig-multiple-projects.sh b/scripts/create-kubeconfig-multiple-projects.sh index f954083..8d7b94e 100644 --- a/scripts/create-kubeconfig-multiple-projects.sh +++ b/scripts/create-kubeconfig-multiple-projects.sh @@ -1,22 +1,67 @@ #!/bin/bash -rm -rf ~/.kube/config +# 1. Check for required dependencies +for cmd in stackit yq; 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 + +# Default path for kubeconfig +KUBECONFIG_PATH="$HOME/.kube/config" + +# 2. Parse command line arguments for a custom filepath +while [[ "$#" -gt 0 ]]; do + case $1 in + -f|--filepath) + KUBECONFIG_PATH="$2" + shift 2 + ;; + -h|--help) + echo "Usage: $0 [-f|--filepath ]" + exit 0 + ;; + *) + echo "Unknown parameter passed: $1" + echo "Usage: $0 [-f|--filepath ]" + exit 1 + ;; + esac +done + +# 3. Safely check if the file exists and ask for confirmation +if [[ -f "$KUBECONFIG_PATH" ]]; then + read -p "The file '$KUBECONFIG_PATH' already exists. Do you want to delete it before continuing? [y/N]: " confirm + if [[ "$confirm" =~ ^[Yy]$ ]]; then + rm "$KUBECONFIG_PATH" + echo "Deleted '$KUBECONFIG_PATH'." + else + echo "Keeping the existing file. New configurations will be merged/appended." + fi +fi + +# Ensure the target directory exists just in case a custom path was provided +mkdir -p "$(dirname "$KUBECONFIG_PATH")" + +# Export the KUBECONFIG environment variable so the STACKIT CLI targets the correct file +export KUBECONFIG="$KUBECONFIG_PATH" + stackit auth login projects=( "xxx-xxx-xxx-xxx" ) - for project in "${projects[@]}"; do stackit config set --project-id "$project" clusters_yaml=$(stackit ske cluster list -o yaml) - cluster_names=$(echo "$clusters_yaml" | yq e '.[] | .name' -) for cluster_name in $cluster_names; do - echo "Creating kubeconfig for cluster: $cluster_name" + echo "Creating kubeconfig for cluster: $cluster_name (saving to $KUBECONFIG_PATH)" stackit ske kubeconfig create --expiration 60d "$cluster_name" -y done done diff --git a/scripts/delete-unused-volumes.sh b/scripts/delete-unused-volumes.sh index 7146b2a..800c732 100644 --- a/scripts/delete-unused-volumes.sh +++ b/scripts/delete-unused-volumes.sh @@ -1,5 +1,14 @@ #!/bin/bash +# 1. Check for required dependencies +for cmd in stackit yq; 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 + # Set to 1 to only print the volumes that would be deleted (no actual deletion) DRY_RUN=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