diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd818f2..880d108 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,7 +66,7 @@ repos: description: Ensures all files have the Apache 2.0 license header entry: addlicense -c "Schwarz Digits Cloud GmbH & Co. KG" -l apache language: system - types_or: [terraform, python, go, javascript, yaml, json] + types_or: [terraform, python, go, javascript, yaml, json, html, css] pass_filenames: true - id: terraform-numbered-files diff --git a/examples/cdn-s3-static-website/.terraform.lock.hcl b/examples/cdn-s3-static-website/.terraform.lock.hcl new file mode 100644 index 0000000..91d9715 --- /dev/null +++ b/examples/cdn-s3-static-website/.terraform.lock.hcl @@ -0,0 +1,68 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.51.0" + constraints = ">= 5.0.0" + hashes = [ + "h1:QWxF+1ePJ4qFCHEc6PyHNeXc865wLvrWVl71d/nABa8=", + "zh:03fcea0a1ea2ca81d62d4d2e2961181bef9068b1c701f2cddc4aa5fac105818a", + "zh:1213944cd623143974ea5c9b70b22ae1ccca33d743924c149ed089d34b8e08b4", + "zh:190a46da0c69082b74da48238ce134d2fc9893e09122ac249c5689f88eab7e13", + "zh:1b312a4b53fa3cf731f95e674c033865feea5455f163b86136f2614424637293", + "zh:2b319814806222c5aba196b1a78756a6b36dc5c91f85edda349234d8a2f20a6a", + "zh:2bddf92c8efc6ad445a2eb8a0e5f88742a0596392c3a4ebc350ebb4105a4a96d", + "zh:3bef0c4f675c09034ff017cf899977b1765b2c0b3d1e489bcb06a5fcac316e2d", + "zh:47c46b5aa22199638fed5c93b195bbfd1182a1408edad4e5c39d4a73a04493f6", + "zh:5f808699650f6db961964466c77f5a581eab142a91c2e54810bb09b6f2fcd3f2", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ada97e6be10164f452e278c23412b8597698a9c95ffb68fe83629d63d85906f3", + "zh:c4d73a91810d8dbcf9abbd431d41fcceebb48f8b6fd3c28a84bb3c6ed08be2e9", + "zh:c63ec875d38fc557b16b0b2b0ab1c7635852799453113240e21a52409de94a71", + "zh:cdd0209a755fc3aa14855aa013dae4b166a2fc7f6d3cbb673f7ff2142f5b63a2", + "zh:e5e665a27290391fd1bffc093ab68b596f6c507785be2e3f0949fab4fd6aec1b", + "zh:f6c42046a31d65eff2793737656b38931f90318b53661046bb84326cd4cb558f", + ] +} + +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/stackitcloud/stackit" { + version = "0.99.0" + constraints = ">= 0.95.0" + hashes = [ + "h1:a9z0j1z/8GmGjz+VygIhgyBbMqxx7jlXGqCvWBDD1NY=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:396c0392b9ef5ec7f8613c29a64e183545cc16dda0ceb876393fc003dba71c73", + "zh:40d86a1fb1c9ed4579583acb8ecc219edca44f9ee5221bfdcbc1bee2ce6654e7", + "zh:4ccbbecc3575737d87195ad13448d06071be9925760a2da5b7e5e8b91517f876", + "zh:506d786647c4566a82487fc3ffe0792f37a63ec8d6b54821aa3c7485e5ed6760", + "zh:848f638c500f1928f8593ae189472add1a0871c1e056d7df06871652ddee3409", + "zh:9ed739aec2c60cdfae3a33e4f349fa630fd0fd0ab50fcec5745774d42a6d6e70", + "zh:c0ac883dd73bd886e419d912c28ec29bb90a611b023cf4ae1b0534945cce1694", + "zh:df28663578694b25453b9d0a1cd7633a0f7fb1c113870cd3c133e9dc05d35946", + "zh:eaacb4a4512f41d44e46f82f042a19ab96c9d90d470890e2fd82c6cafb33bf0e", + "zh:ef9dd9b10571804f3a4dd6062405d0e473df270d75f05f897901c54d7d6c3d9d", + "zh:f40add9cd4fd4a7cda53f4a418c5f47a220b5ba5c4fc2377f60b1e16368f87d9", + "zh:f65deb30c1e3e8018a888d1aed56e895ea1e26b880f22a5772771e9836c9b5a4", + "zh:f8d14feddfd9d785d3ee6469937234a631998758ea5e8c16ecf61cdb94b07564", + ] +} diff --git a/examples/cdn-s3-static-website/010-provider.tf b/examples/cdn-s3-static-website/010-provider.tf new file mode 100644 index 0000000..a492b98 --- /dev/null +++ b/examples/cdn-s3-static-website/010-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_version = ">= 1.5" + + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.95.0" + } + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path + enable_beta_resources = true +} + +provider "aws" { + region = var.stackit_region + + access_key = stackit_objectstorage_credential.cdn.access_key + secret_key = stackit_objectstorage_credential.cdn.secret_access_key + + skip_credentials_validation = true + skip_region_validation = true + skip_requesting_account_id = true + skip_metadata_api_check = true + s3_use_path_style = true + + endpoints { + s3 = "https://object.storage.${var.stackit_region}.onstackit.cloud" + } +} diff --git a/examples/cdn-s3-static-website/020-variables.tf b/examples/cdn-s3-static-website/020-variables.tf new file mode 100644 index 0000000..e5fa7ee --- /dev/null +++ b/examples/cdn-s3-static-website/020-variables.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. + +variable "stackit_project_id" { + description = "The STACKIT project ID where the CDN distribution will be created" + type = string +} + +variable "stackit_region" { + description = "The STACKIT region" + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + description = "Path to the STACKIT service account key JSON file" + type = string +} + +variable "cdn_enabled_regions" { + description = "List of CDN regions to enable. Valid values: EU, US, ASIA, AF, SA" + type = list(string) + default = ["EU"] +} + +variable "cdn_blocked_countries" { + description = "List of ISO 3166-1 alpha-2 country codes to block" + type = list(string) + default = [] +} diff --git a/examples/cdn-s3-static-website/030-object-storage.tf b/examples/cdn-s3-static-website/030-object-storage.tf new file mode 100644 index 0000000..d536daa --- /dev/null +++ b/examples/cdn-s3-static-website/030-object-storage.tf @@ -0,0 +1,31 @@ +# 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 "random_pet" "bucket_name" {} + +resource "stackit_objectstorage_bucket" "website" { + project_id = var.stackit_project_id + name = random_pet.bucket_name.id +} + +resource "stackit_objectstorage_credentials_group" "cdn" { + project_id = var.stackit_project_id + name = "cdn-credentials-${random_pet.bucket_name.id}" + depends_on = [stackit_objectstorage_bucket.website] +} + +resource "stackit_objectstorage_credential" "cdn" { + project_id = var.stackit_project_id + credentials_group_id = stackit_objectstorage_credentials_group.cdn.credentials_group_id +} diff --git a/examples/cdn-s3-static-website/040-bucket-setup.tf b/examples/cdn-s3-static-website/040-bucket-setup.tf new file mode 100644 index 0000000..9f11bd1 --- /dev/null +++ b/examples/cdn-s3-static-website/040-bucket-setup.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. + +resource "aws_s3_bucket_policy" "public_read" { + bucket = stackit_objectstorage_bucket.website.name + policy = jsonencode({ + Statement = [ + { + Sid = "PublicReadGetObject" + Effect = "Allow" + Principal = "*" + Action = "s3:GetObject" + Resource = "urn:sgws:s3:::${stackit_objectstorage_bucket.website.name}/*" + } + ] + }) +} + +resource "aws_s3_object" "index_html" { + bucket = stackit_objectstorage_bucket.website.name + key = "index.html" + source = "${path.module}/files/index.html" + + content_type = "text/html" + etag = filemd5("${path.module}/files/index.html") +} + +resource "aws_s3_object" "about_html" { + bucket = stackit_objectstorage_bucket.website.name + key = "about.html" + source = "${path.module}/files/about.html" + + content_type = "text/html" + etag = filemd5("${path.module}/files/about.html") +} + +resource "aws_s3_object" "static_css" { + bucket = stackit_objectstorage_bucket.website.name + key = "static/style.css" + source = "${path.module}/files/static/style.css" + + content_type = "text/css" + etag = filemd5("${path.module}/files/static/style.css") +} diff --git a/examples/cdn-s3-static-website/050-cdn.tf b/examples/cdn-s3-static-website/050-cdn.tf new file mode 100644 index 0000000..3492aac --- /dev/null +++ b/examples/cdn-s3-static-website/050-cdn.tf @@ -0,0 +1,86 @@ +# 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 { + bucket_endpoint = "https://${stackit_objectstorage_bucket.website.name}.object.storage.${var.stackit_region}.onstackit.cloud" + blocked_countries = length(var.cdn_blocked_countries) > 0 ? var.cdn_blocked_countries : null +} + +resource "stackit_cdn_distribution" "website" { + project_id = var.stackit_project_id + config = { + backend = { + type = "bucket" + bucket_url = local.bucket_endpoint + region = var.stackit_region + credentials = { + access_key_id = stackit_objectstorage_credential.cdn.access_key + secret_access_key = stackit_objectstorage_credential.cdn.secret_access_key + } + } + + regions = var.cdn_enabled_regions + blocked_countries = local.blocked_countries + + optimizer = { + enabled = false + } + + /*redirects = { + rules = [ + { + description = "Redirect / path to index" + enabled = true + rule_match_condition = "ANY" + status_code = 301 + target_url = "/" + matchers = [ + { + values = ["/index"] + value_match_condition = "ANY" + } + ] + } + ] + }*/ + + /*waf = { + mode = "ENABLED" + type = "FREE" + paranoia_level = "L1" + + enabled_rule_collection_ids = ["@builtin/crs/request"] + disabled_rule_collection_ids = [] + log_only_rule_collection_ids = ["@builtin/crs/response"] + + enabled_rule_group_ids = [] + disabled_rule_group_ids = [] + log_only_rule_group_ids = [] + + enabled_rule_ids = [] + disabled_rule_ids = [] + log_only_rule_ids = [] + + allowed_http_versions = ["HTTP/1.1", "HTTP/2"] + allowed_http_methods = ["GET", "HEAD"] + allowed_request_content_types = ["text/html", "text/css", "text/plain", "application/javascript"] + }*/ + } + + depends_on = [ + aws_s3_object.index_html, + aws_s3_object.about_html, + aws_s3_object.static_css, + ] +} diff --git a/examples/cdn-s3-static-website/060-outputs.tf b/examples/cdn-s3-static-website/060-outputs.tf new file mode 100644 index 0000000..8898b28 --- /dev/null +++ b/examples/cdn-s3-static-website/060-outputs.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. + +output "cdn_distribution_id" { + description = "The STACKIT CDN distribution ID" + value = stackit_cdn_distribution.website.distribution_id +} + +output "cdn_managed_domain" { + description = "The managed CDN domain for the distribution" + value = stackit_cdn_distribution.website.domains[0].name +} + +output "cdn_status" { + description = "The current status of the CDN distribution" + value = stackit_cdn_distribution.website.status +} + +output "bucket_name" { + description = "The STACKIT Object Storage bucket name" + value = stackit_objectstorage_bucket.website.name +} diff --git a/examples/cdn-s3-static-website/MAINTAINERS.md b/examples/cdn-s3-static-website/MAINTAINERS.md new file mode 100644 index 0000000..1aaefce --- /dev/null +++ b/examples/cdn-s3-static-website/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/cdn-s3-static-website/README.md b/examples/cdn-s3-static-website/README.md new file mode 100644 index 0000000..25538aa --- /dev/null +++ b/examples/cdn-s3-static-website/README.md @@ -0,0 +1,119 @@ +# Static Website on STACKIT CDN with S3 Backend + +A reference implementation showing how to deploy a static website using [STACKIT CDN](https://stackit.com/en/products/network/stackit-cdn) with [STACKIT Object Storage](http://stackit.com/en/products/storage/stackit-object-storage) as the origin. + +--- + +## Architecture + +```mermaid +flowchart LR + U(User) -->|HTTPS request| Edge(CDN Edge) + Edge --> B(Bucket) + Edge -->|cached response| U + + subgraph WAF + Edge + end +``` + +--- + +## Prerequisites + +- [Terraform](https://developer.hashicorp.com/terraform/install) >= 1.5 +- A STACKIT project +- A STACKIT service account with sufficient permissions +- A STACKIT service account JSON key file + +--- + +## Setup + +Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values, then run: + +```bash +terraform init +terraform apply +``` + +After `apply`, visit the output domain: + +``` +cdn_managed_domain = "https://abc123.stackit.cdn" +``` + +--- + +## Configuration + +### Variables + +| Variable | Description | Default | +| ---------------------------------- | ------------------------------------------- | -------- | +| `stackit_project_id` | STACKIT project ID | — | +| `stackit_service_account_key_path` | Path to SA key JSON | — | +| `stackit_region` | STACKIT region | `eu01` | +| `cdn_enabled_regions` | CDN regions: `EU`, `US`, `ASIA`, `AF`, `SA` | `["EU"]` | +| `cdn_blocked_countries` | ISO 3166-1 alpha-2 codes to block | `[]` | + +--- + +## Verify Redirect and WAF + +### Redirect + +The example configures a **301 redirect** from `/old/home` to `/`. Test it: + +```bash +URL=$(terraform output -raw cdn_managed_domain) + +# Should return 301 with Location header pointing to / +curl -sI "${URL}/old/home" + +# Direct access should return 200 +curl -sI "${URL}/" +``` + +Expected redirect response: + +``` +HTTP/2 301 +location: / +... +``` + +### WAF + +The example enables the WAF in `ENABLED` mode with `@builtin/crs/request` rules, restricting accepted methods to `GET` and `HEAD`. + +```bash +URL=$(terraform output -raw cdn_managed_domain) + +# GET request — allowed (200) +curl -sI "${URL}/" + +# POST request — blocked by WAF (403) +curl -sI -X POST "${URL}/" + +# HEAD request — allowed (200) +curl -sI --head "${URL}/" +``` + +If the WAF blocks the `POST` request correctly, you'll see a `403 Forbidden` response. If you see `200 OK`, the WAF is either not yet active or misconfigured. + +--- + +## Cleanup + +```bash +terraform destroy +``` + +--- + +## References + +- [STACKIT CDN Documentation](https://docs.stackit.cloud/products/network/load-balancing-and-content-delivery/cdn) +- [STACKIT Object Storage Documentation](https://docs.stackit.cloud/products/storage/object-storage) +- [STACKIT Terraform Provider](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs) diff --git a/examples/cdn-s3-static-website/files/about.html b/examples/cdn-s3-static-website/files/about.html new file mode 100644 index 0000000..db14046 --- /dev/null +++ b/examples/cdn-s3-static-website/files/about.html @@ -0,0 +1,40 @@ + + + + +
+ + +This is a reference implementation showing how to:
+This static website is served via a STACKIT Content Delivery Network distribution.
+The content is stored in a STACKIT Object Storage bucket and cached at edge locations.
+ + +