example(iaas): add example on how to use alb with waf integrations #14

Merged
mauritz.uphoff merged 1 commit from example/l7-alb-example into main 2026-04-21 13:03:21 +00:00
23 changed files with 505 additions and 4 deletions

View file

@ -21,7 +21,7 @@ module "test-machine01" {
name = "machine01"
machine_type = var.jumphost_flavor
disk_size = 500
disk_size = 48
user_data = templatefile("${path.module}/apache-debug-user.yaml", {})
}

View file

@ -21,7 +21,7 @@ module "test-machine02" {
name = "machine02"
machine_type = var.jumphost_flavor
disk_size = 500
disk_size = 48
user_data = templatefile("${path.module}/apache-debug-user.yaml", {})
}

View file

@ -1,5 +1,5 @@
# IaaS cross AZ Loadbalancer
# 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 Load Balancer.
A classic highly-available architecture: provisioning multiple VMs across different Availability Zones (AZs) and putting them behind a STACKIT L4 Load Balancer.

View file

@ -0,0 +1 @@
v1.14.0

View file

@ -0,0 +1,86 @@
# 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: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: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: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=",
"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",
]
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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,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}/"
```

View file

@ -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 "<h1>Hello from STACKIT Instance</h1><p>Hostname $(hostname)</p>" > /var/www/html/index.html
- chown www-data:www-data /var/www/html/index.html

View file

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