↓Aller au contenu
  1. Blog Posts/

Terraform n'est pas qu'un outil d'infra : Comment j'ai piloté Authentik depuis des values

5 mins·
Terraform Authentik Sso Oidc Passkey Homelab Infrastructure-as-Code
Sommaire

Terraform ne provisionne pas que « de l’infra »
#

Quand on parle de Terraform, tout le monde pense VM, réseaux, buckets, clusters. C’est son usage le plus connu, mais c’est une vision réductrice.

Au fond, Terraform ne sait rien faire tout seul. C’est une interface entre une configuration déclarée dans des fichiers et l’API d’un outil. Toute l’intelligence métier vit dans le provider : c’est lui qui sait transformer un bloc HCL en requêtes API, et dans quel ordre.

Le cycle est toujours le même :

  1. Tu déclares l’état voulu dans des fichiers.
  2. Terraform compare cet état avec le tfstate (ce qu’il a appliqué la dernière fois) et avec ce qui existe réellement derrière l’API.
  3. Le provider traduit la différence en appels POST, PATCH ou DELETE, et rien de plus.

Que l’API en face crée une VM chez un hyperviseur ou une application dans un IdP, Terraform s’en moque. Si l’outil a une API et un provider, il se pilote comme de l’infra, avec la même mécanique de plan, d’apply et de détection du drift.

C’est exactement ce que j’ai fait avec Authentik.

Avant : 50 interfaces pour une application
#

Authentik, c’est l’IdP de mon lab. Tout ce qui sait parler OIDC passe par lui : ArgoCD, Grafana, PMM, Nextcloud, Velero UI, ByteStash.

Le problème, c’est que brancher une application demande de passer par plusieurs écrans :

  • créer le provider OAuth2, choisir le flow d’autorisation, le flow d’invalidation, le certificat de signature, les scopes, les redirect URIs ;
  • créer l’application et la rattacher au provider ;
  • créer les policy bindings pour dire quels groupes y ont accès ;
  • vérifier que les groupes existent et que les utilisateurs sont dedans ;
  • récupérer le client_id et le secret pour les coller dans l’application en face.

Multiplié par six applications, c’était six fois le même parcours, avec à chaque fois le risque d’oublier un binding et de laisser une application ouverte à tout le monde. Et au bout de trois mois, impossible de dire de tête qui a accès à quoi sans refaire le tour des écrans.

Le but : une application = un bloc
#

Mon objectif était simple : ajouter une application doit se résumer à ajouter un bloc dans un fichier de values. Tout ce qui est commun à mes applications (flows, certificat, scopes, type de client) est décidé une fois pour toutes dans le code. Il ne reste dans les values que ce qui change vraiment d’une application à l’autre.

Voilà à quoi ressemble mon terraform.tfvars :

groups = ["wheezy-admins", "grafana-admins", "wheezy-users"]

users = {
  florianspk = {
    name   = "Florian"
    email  = "[email protected]"
    groups = ["wheezy-admins", "grafana-admins"]
  }
}

oauth2_applications = {
  grafana = {
    name               = "Grafana"
    group              = "Observability"
    icon               = "https://cdn.jsdelivr.net/gh/selfhst/icons@main/png/grafana.png"
    redirect_uris      = ["https://grafana.wheezy.lab/login/generic_oauth"]
    restrict_to_groups = ["wheezy-admins", "grafana-admins"]
  }

  nextcloud = {
    name               = "Nextcloud"
    group              = "Users"
    redirect_uris      = ["https://drive.wheezy.lab/apps/user_oidc/code"]
    restrict_to_groups = ["wheezy-admins", "wheezy-users"]
  }
}

C’est tout. Pas d’UUID de flow, pas d’ID de certificat, pas de mapping de scopes. Qui a accès à quoi se lit en une seconde.

Comment c’est construit
#

Le repo est volontairement petit :

backend.tf        state dans Garage (S3)
data.tf           flows par défaut, certificat, scopes
identity.tf       groupes et utilisateurs
main.tf           un module par application OIDC
passkey.tf        login par passkey
modules/oauth2-application/

Les data sources vont chercher une seule fois ce qu’Authentik fournit déjà : le flow default-provider-authorization-implicit-consent, le flow d’invalidation, le certificat auto-signé et les mappings des scopes openid, email et profile. C’est ce qui permet de ne jamais écrire un UUID à la main.

Le module oauth2-application crée les trois objets qu’il fallait créer à la main :

resource "authentik_provider_oauth2" "this" {
  name        = var.name
  client_id   = var.slug
  client_type = "confidential"
  grant_types = ["authorization_code", "refresh_token"]

  allowed_redirect_uris = [
    for uri in var.redirect_uris : {
      matching_mode     = "strict"
      url               = uri
      redirect_uri_type = "authorization"
    }
  ]
  # flows, certificat et scopes injectés depuis les data sources
}

resource "authentik_application" "this" {
  name              = var.name
  slug              = var.slug
  protocol_provider = authentik_provider_oauth2.this.id
}

resource "authentik_policy_binding" "restrict" {
  for_each = var.restrict_to_group_ids
  target   = authentik_application.this.uuid
  group    = each.value
  order    = index(keys(var.restrict_to_group_ids), each.key)
}

Le client_id est la clé du map : grafana, pmm, nextcloud. Lisible, prévisible, et pas besoin d’aller le chercher dans l’UI.

Les groupes sont l’unité de droit. Une application déclare les groupes autorisés, un utilisateur déclare ses groupes. Donner accès à Grafana à quelqu’un, c’est ajouter un groupe dans sa liste. Les applications sont en policy_engine_mode = any : il suffit d’appartenir à un des groupes pour passer.

Et pour récupérer les secrets :

terraform output -json oauth2_applications | jq -r '.grafana.client_secret'

Pour résumer
#

Terraform n’est pas un outil d’infra : c’est un moteur de réconciliation entre des fichiers et des API. Dès qu’un outil a un provider, il peut se gérer comme le reste de ton infra, avec un état déclaré, un plan relu et un historique dans Git.

Pour Authentik, le résultat tient en quelques fichiers :

  • ajouter une application, c’est un bloc de six lignes ;
  • savoir qui a accès à quoi, c’est lire un fichier ;
  • changer le login de tout le SSO, c’est un plan relu puis un apply, avec un retour arrière d’une ligne.

Et surtout, je n’ouvre plus l’interface d’admin d’Authentik que pour regarder les logs.

Articles connexes

Sauvegarder Kubernetes : Il ne faut plus penser comme un admin VMware
9 mins
Kubernetes Velero Linstor Csi Backup Gitops
Stockage hyperconvergé Kubernetes avec DRBD + LINSTOR
5 mins
Infrastructures Kubernetes Storage DevOps Homelab
CKS — Comment j'ai obtenu 91% à la Certified Kubernetes Security Specialist
7 mins
Certifications Kubernetes Cks Certification DevOps Sre Securite
Comment j'ai accéléré mes backups perso avec Garage (S3) et Restic
3 mins
Infrastructure Tutoriel Backup Self-Hosted S3 Garage Restic Nas
Veille technologique intelligente : 0€ par mois grâce à n8n et Mistral AI
5 mins
Middleware Kubernetes N8n Self-Hosting Web Veille Ia Automatisation