example(cdn): add example on how to use cdn with s3 #36

Merged
mauritz.uphoff merged 1 commit from example/cdn-s3-static-hosting into main 2026-06-23 13:16:22 +00:00
14 changed files with 661 additions and 1 deletions

View file

@ -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

View file

@ -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",
]
}

View file

@ -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"
}
}

View file

@ -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 = []
}

View file

@ -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
}

View file

@ -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")
}

View file

@ -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,
]
}

View file

@ -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
}

View file

@ -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.

View file

@ -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)

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<!--
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.
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About - STACKIT CDN Example</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h1>About this Demo</h1>
<p>This is a reference implementation showing how to:</p>
<ul>
<li>Host static files on <a href="http://stackit.com/en/products/storage/stackit-object-storage">STACKIT Object Storage</a></li>
<li>Front them with <a href="https://stackit.com/en/products/network/stackit-cdn">STACKIT CDN</a> for low-latency delivery</li>
<li>Configure bucket policies and CDN distributions via Terraform</li>
</ul>
<p><a href="/">← Back to home</a></p>
<footer>
<p>&copy; 2026 STACKIT | CDN Example</p>
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<!--
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.
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>STACKIT CDN Example</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h1>Welcome to STACKIT CDN</h1>
<p>This static website is served via a <a href="https://stackit.com/en/products/network/stackit-cdn">STACKIT Content Delivery Network</a> distribution.</p>
<p>The content is stored in a STACKIT Object Storage bucket and cached at edge locations.</p>
<nav>
<ul>
<li><a href="/about.html">About</a></li>
</ul>
</nav>
<footer>
<p>&copy; 2026 STACKIT | CDN Example</p>
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,82 @@
/**
* 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.
*/
:root {
--primary-color: #e85d04;
--text-color: #2b2d42;
--bg-color: #f8f9fa;
--card-bg: #ffffff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
max-width: 680px;
margin: 2rem auto;
padding: 2.5rem;
background: var(--card-bg);
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0,0,0,0.08);
}
h1 {
font-size: 2rem;
color: var(--primary-color);
margin-bottom: 1rem;
}
p {
margin-bottom: 1rem;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
nav ul {
list-style: none;
margin: 1.5rem 0;
display: flex;
gap: 1rem;
}
footer {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #e9ecef;
font-size: 0.85rem;
color: #6c757d;
}

View file

@ -0,0 +1,5 @@
stackit_project_id = "your-project-id"
stackit_region = "eu01"
stackit_service_account_key_path = "./keys/service-account.json"
cdn_enabled_regions = ["EU"]
cdn_blocked_countries = []