example(idp): build a example on how to use scim #23

Merged
mauritz.uphoff merged 2 commits from example/scim-integration into main 2026-05-19 13:42:43 +00:00
20 changed files with 877 additions and 0 deletions

View file

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

View file

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

View file

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

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

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

View file

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

View file

@ -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 = [
<<EOF
controller:
replicaCount: 1
service:
type: LoadBalancer
annotations:
lb.stackit.cloud/ip-mode-proxy: "true"
lb.stackit.cloud/external-address: ${stackit_public_ip.ingress_floating_ip.ip}
EOF
]
timeout = 600
}

View file

@ -0,0 +1,98 @@
# 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_password" "authentik_secret_key" {
length = 50
special = true
}
resource "random_password" "authentik_bootstrap_password" {
length = 24
special = true
}
resource "random_password" "authentik_bootstrap_token" {
length = 40
special = false
}
resource "random_password" "postgresql_password" {
length = 24
special = false
}
locals {
authentik_values = {
authentik = {
secret_key = random_password.authentik_secret_key.result
bootstrap_password = random_password.authentik_bootstrap_password.result
bootstrap_token = random_password.authentik_bootstrap_token.result
postgresql = {
user = "authentik"
name = "authentik"
password = random_password.postgresql_password.result
}
}
postgresql = {
enabled = true
auth = {
username = "authentik"
database = "authentik"
password = random_password.postgresql_password.result
}
}
server = {
ingress = {
enabled = true
ingressClassName = "nginx"
annotations = {
"cert-manager.io/cluster-issuer" = "letsencrypt-prod-cluster"
}
hosts = [
"${stackit_dns_record_set.authentik.name}.${stackit_dns_zone.this.dns_name}"
]
paths = ["/"]
tls = [
{
secretName = "authentik-tls"
hosts = [
"${stackit_dns_record_set.authentik.name}.${stackit_dns_zone.this.dns_name}"
]
}
]
}
}
}
}
resource "helm_release" "authentik" {
name = "authentik"
repository = "https://charts.goauthentik.io"
chart = "authentik"
version = "2026.2.3"
namespace = kubernetes_namespace_v1.authentik.metadata.0.name
values = [
yamlencode(local.authentik_values)
]
timeout = 600
}
resource "time_sleep" "wait_60_seconds" {
depends_on = [helm_release.authentik]
create_duration = "60s"
}

View file

@ -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.
resource "authentik_user" "test_users" {
count = var.authentik_number_of_users
username = "testuser${count.index + 1}"
name = "Test User ${count.index + 1}"
email = "testuser${count.index + 1}@${stackit_dns_zone.this.dns_name}"
password = var.authentik_default_user_password
attributes = jsonencode({
given_name = "Test${count.index + 1}"
family_name = "User ${count.index + 1}"
preferred_username = "testuser${count.index + 1}"
})
depends_on = [time_sleep.wait_60_seconds]
}
resource "authentik_group" "stackit_test_user" {
name = "stackit-admins"
users = authentik_user.test_users[*].id
depends_on = [time_sleep.wait_60_seconds]
}
data "authentik_property_mapping_provider_scope" "scopes" {
managed_list = [
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-profile"
]
depends_on = [time_sleep.wait_60_seconds]
}

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.
resource "random_password" "authentik_client_secret" {
length = 40
special = true
}
data "authentik_flow" "default_authorization_flow" {
slug = "default-provider-authorization-implicit-consent"
depends_on = [time_sleep.wait_60_seconds]
}
data "authentik_flow" "default_invalidation_flow" {
slug = "default-provider-invalidation-flow"
depends_on = [time_sleep.wait_60_seconds]
}
resource "authentik_property_mapping_provider_scope" "stackit_custom_claims" {
name = "stackit-custom-claims"
scope_name = "profile" # Attaches this data to the standard 'profile' scope
expression = <<EOT
return {
"given_name": request.user.attributes.get("given_name", request.user.name),
"family_name": request.user.attributes.get("family_name", request.user.name),
"preferred_username": request.user.attributes.get("preferred_username", request.user.username)
}
EOT
}
data "authentik_certificate_key_pair" "this" {
name = "authentik Self-signed Certificate"
}
resource "authentik_provider_oauth2" "stackit" {
name = "stackit"
client_id = "stackit-client"
client_secret = random_password.authentik_client_secret.result
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
allowed_redirect_uris = [
{
matching_mode = "strict"
url = "https://accounts.stackit.cloud/ui/login/login/externalidp/callback"
},
# debugging
{
matching_mode = "strict"
url = "http://localhost:8080/ui/login/login/externalidp/callback"
}
]
signing_key = data.authentik_certificate_key_pair.this.id
property_mappings = concat(
data.authentik_property_mapping_provider_scope.scopes.ids,
[authentik_property_mapping_provider_scope.stackit_custom_claims.id]
)
include_claims_in_id_token = true
depends_on = [time_sleep.wait_60_seconds]
lifecycle {
prevent_destroy = true
}
}

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.
data "authentik_property_mapping_provider_scim" "scim_user" {
managed_list = [
"goauthentik.io/providers/scim/user"
]
}
data "authentik_property_mapping_provider_scim" "scim_group" {
managed_list = [
"goauthentik.io/providers/scim/group"
]
}
resource "authentik_provider_scim" "stackit" {
name = "stackit-scim"
url = "https://accounts.stackit.cloud/scim/v2/"
token = var.authentik_scim_long_lived_token
property_mappings = data.authentik_property_mapping_provider_scim.scim_user.ids
property_mappings_group = data.authentik_property_mapping_provider_scim.scim_group.ids
exclude_users_service_account = true
}
resource "authentik_application" "stackit" {
name = "STACKIT"
slug = "stackit"
protocol_provider = authentik_provider_oauth2.stackit.id
# Connects the SCIM provisioning pipeline to this application
backchannel_providers = [
authentik_provider_scim.stackit.id
]
}

View file

@ -0,0 +1,49 @@
# 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 "authentik_url" {
value = "https://${stackit_dns_record_set.authentik.name}.${stackit_dns_zone.this.dns_name}"
}
output "authentik_oidc_issuer" {
description = "Issuer identifier URL for your OIDC provider"
value = "https://${stackit_dns_record_set.authentik.name}.${stackit_dns_zone.this.dns_name}/application/o/stackit/"
}
output "authentik_oidc_client_id" {
description = "ID assigned to our application"
value = authentik_provider_oauth2.stackit.client_id
}
output "authentik_oidc_client_secret" {
description = "Secret key associated with the Client ID"
value = random_password.authentik_client_secret.result
sensitive = true
}
output "stackit_ticket_scopes" {
description = "Required permissions to include in the STACKIT Support Ticket"
value = "openid email profile"
}
output "stackit_ticket_claims_mapping" {
description = "Standard Authentik claims mapping to copy into the STACKIT Support Ticket"
value = {
unique_user_id = "sub"
email_address = "email"
preferred_name = "preferred_username" # Or "name"
first_name = "given_name"
last_name = "family_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,104 @@
# STACKIT IAM-SCIM Integration with Authentik
This repository provides an automated setup for **Authentik** on STACKIT SKE, pre-configured as an Identity Provider (IdP) for STACKIT with both **OIDC** and **SCIM** support.
## Integration Details
### OAuth2 / OIDC
Authentik acts as the OIDC issuer. The provider is configured with the following:
- **Client ID**: `stackit-client`
- **Scopes**: `openid`, `email`, `profile`
- **Custom Claims**: Maps `given_name`, `family_name`, and `preferred_username` from Authentik user attributes.
### SCIM Provisioning
Automated user and group synchronization to STACKIT:
- **Endpoint**: `https://accounts.stackit.cloud/scim/v2/`
- **Authentication**: Uses a long-lived token (required for Authentik Community Edition).
- **Mapping**: Synchronizes both Users and Groups (e.g., `stackit-admins`).
---
## ⚠️ STACKIT Integration Process
**Self-service provisioning for configuring external Identity Providers is currently a Work In Progress.** Until this is released, you must request the integration by opening a STACKIT support ticket.
### What to supply in your ticket:
Please open a support ticket with STACKIT containing the following details:
**General Information**
- **Federation type:** OpenID Connect (OIDC)
- **Reason for integration:** Brief explanation (e.g., "Enable SSO and SCIM for enterprise users via Authentik")
- **Email domains:** All email domains your employees use for login (e.g., `@example.com` and `@foobar.com`)
**OIDC-Specific Information**
- **Issuer:** The Issuer identifier URL for your Authentik instance (e.g., `https://authentik.example.com/`)
- **Client ID:** The ID assigned to the application (`stackit-client`)
- **Client Secret:** The secret key associated with your Client ID _(Note: Provide this securely!)_
- **Scopes:** `openid`, `profile`, `email`
- **Display name:** Internal name for this federation (e.g., `my_company_authentik`)
- **Claims mapping:** \* Unique user ID -> `sub`
- Email address -> `email`
- Preferred name -> `preferred_username`
- First name -> `given_name`
- Last name -> `family_name`
### What you will receive in return:
Once STACKIT support processes your ticket, they will configure the trust relationship on their end. You will receive:
1. **Confirmation of Federation:** Your Authentik instance will officially be trusted by the STACKIT login portal.
2. **SCIM Credentials:** You will be provided with the required OAuth credentials to generate the necessary Bearer tokens so Authentik can communicate with the STACKIT SCIM API.
---
## Testing the SCIM Integration
### Scenario 1: User Sync
1. **Create a User**: In the Authentik UI (_Directory -> Users_), create a new test user.
2. **Assign to Application**: Ensure the user is assigned to the `STACKIT` application.
3. **Verify**: Log in to the STACKIT Portal. If the user doesn't appear immediately, go to _Applications -> STACKIT -> Backchannel Providers_ and click **Sync Now**.
### Scenario 2: Group & Role Mapping (RBAC)
1. **Create/Assign Group**: Add your user to the `stackit-admins` group in Authentik.
2. **Map to STACKIT Role**: In the STACKIT Org settings, map this group to the `Owner` or `Admin` role.
3. **Verify Access**:
- Log in to the STACKIT Portal. The user should have the assigned organization-level permissions.
- **Remove Group**: Remove the user from the group in Authentik. After sync, the user's permissions in the STACKIT Org will be revoked.
---
## Visual Verification
### 1. Dashboard/Application Overview
![Dashboard](docs/authentik-dashboard-overview.png)
![Application](docs/authentik-application-overview.png)
### 2. User & Group Management
![Groups](docs/authentik-user-management.png)
![Provider](docs/authentik-group-management.png)
### 3. SCIM Sync
![Scim](docs/authentik-scim-sync.png)
### 4. Group on STACKIT Side
![Stackit-group-sync](docs/search-for-group-stackit-admins.png)
---
## References & Documentation
- [Generic OIDC 2.0 Federation Guide](https://docs.stackit.cloud/platform/access-and-identity/stackit-idp/how-tos/generic-oidc-2_0-federation-guide/)
- [SCIM Endpoint STACKIT IdP Guide](https://docs.stackit.cloud/platform/access-and-identity/stackit-idp/how-tos/scim-endpoint/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB