diff --git a/config/providers_saml/ceph.yaml b/config/providers_saml/ceph.yaml
new file mode 100644
index 0000000..60a5af5
--- /dev/null
+++ b/config/providers_saml/ceph.yaml
@@ -0,0 +1,15 @@
+# SAML provider + application for the Ceph dashboard (dashboard.ceph.unkin.net).
+# Ceph dashboard SSO is SAML 2.0 (no native OIDC). The SP entity id and ACS URL
+# are derived by Ceph from its base URL:
+# entity id (audience): /auth/saml2/metadata
+# ACS url (HTTP-POST): /auth/saml2
+# Configure the Ceph side (Puppet/mgr) with:
+# ceph dashboard sso setup saml2 https://dashboard.ceph.unkin.net
+name: Ceph Dashboard
+authorization_flow: default-provider-authorization-implicit-consent
+invalidation_flow: default-provider-invalidation-flow
+acs_url: https://dashboard.ceph.unkin.net/auth/saml2
+audience: https://dashboard.ceph.unkin.net/auth/saml2/metadata
+sp_binding: post
+# Authentik's built-in self-signed keypair, resolved by name; signs assertions.
+signing_kp: authentik Self-signed Certificate
diff --git a/modules/authentik/main.tf b/modules/authentik/main.tf
index fa84126..a92444e 100644
--- a/modules/authentik/main.tf
+++ b/modules/authentik/main.tf
@@ -52,17 +52,34 @@ resource "authentik_property_mapping_provider_scope" "groups_hierarchical" {
EOT
}
+# Resolve SAML flows by slug and the signing keypair by name, so configs use
+# human-readable names instead of Authentik UUIDs (mirrors the oauth2 handling).
+data "authentik_flow" "saml_authorization" {
+ for_each = var.providers_saml
+ slug = each.value.authorization_flow
+}
+
+data "authentik_flow" "saml_invalidation" {
+ for_each = var.providers_saml
+ slug = each.value.invalidation_flow
+}
+
+data "authentik_certificate_key_pair" "saml_signing" {
+ for_each = { for k, v in var.providers_saml : k => v if v.signing_kp != null }
+ name = each.value.signing_kp
+}
+
resource "authentik_provider_saml" "this" {
for_each = var.providers_saml
name = each.value.name
- authorization_flow = each.value.authorization_flow
- invalidation_flow = each.value.invalidation_flow
+ authorization_flow = data.authentik_flow.saml_authorization[each.key].id
+ invalidation_flow = data.authentik_flow.saml_invalidation[each.key].id
acs_url = each.value.acs_url
sp_binding = each.value.sp_binding
audience = each.value.audience
name_id_mapping = each.value.name_id_mapping
- signing_kp = each.value.signing_kp
+ signing_kp = each.value.signing_kp != null ? data.authentik_certificate_key_pair.saml_signing[each.key].id : null
}
# Resolve oauth2 flows by slug and scope mappings by managed identifier, and