Architecture de référence¶
Vue d'ensemble¶
L'architecture cible repose sur quatre builds MkDocs paralleles, chacun produisant un site statique correspondant a un niveau de classification. Un reverse proxy authentifie les utilisateurs via Keycloak (OIDC) et les route vers le build adequat en fonction d'une claim extraite du token.
graph TD
subgraph Sources["Sources (Gitea)"]
MD["Fichiers Markdown<br/>+ front matter classification"]
end
subgraph CI["CI/CD (Gitea Actions)"]
BP["build-public<br/>MKDOCS_CLASSIFICATION=public"]
BI["build-interne<br/>MKDOCS_CLASSIFICATION=interne"]
BC["build-confidentiel<br/>MKDOCS_CLASSIFICATION=confidentiel"]
BR["build-restreint<br/>MKDOCS_CLASSIFICATION=restreint"]
end
subgraph Server["Serveur de documentation"]
DP["/var/www/docs/public/"]
DI["/var/www/docs/interne/"]
DC["/var/www/docs/confidentiel/"]
DR["/var/www/docs/restreint/"]
end
subgraph Proxy["Reverse Proxy (Caddy)"]
OIDC["Auth OIDC<br/>(Keycloak)"]
Route["Routage par claim<br/>doc_classification"]
end
MD --> BP & BI & BC & BR
BP --> DP
BI --> DI
BC --> DC
BR --> DR
DP & DI & DC & DR --> Route
OIDC --> Route Le principe est simple : les fichiers sources sont uniques et communs a tous les builds. C'est le plugin de filtrage qui, au moment du build, ecarte les pages dont la classification depasse le niveau cible. L'utilisateur final voit un seul domaine ; le proxy s'occupe de lui servir la version correspondant a ses droits.
Front matter et marquage¶
Convention de classification¶
Chaque fichier Markdown peut déclarer son niveau de classification dans son front matter YAML via la cle classification :
---
title: Procedure de deploiement production
description: Etapes de deploiement sur l'environnement de production
classification: confidentiel
---
Les valeurs acceptees sont au nombre de quatre :
| Valeur | Signification |
|---|---|
public | Accessible sans authentification |
interne | Reserve aux employes authentifies |
confidentiel | Reserve aux équipes ayant un accès explicite |
restreint | Reserve aux personnes nommement habilitees |
Regles de validation¶
Deux regles sont appliquees a la compilation :
- Valeur absente : la page est traitee comme
public. Cette convention garantit la compatibilité ascendante avec les fichiers existants non encore marques. - Valeur inconnue : le build échoue immédiatement (stratégie fail-fast). Une faute de frappe sur la classification est une erreur de sécurité potentielle et ne doit pas etre ignoree silencieusement.
Fail-fast sur valeur inconnue
Une classification non reconnue (ex. confidentiel avec une espace, Interne avec majuscule) interrompt le build avec une erreur explicite. Ce comportement est intentionnel : mieux vaut un build rouge qu'une page confidentielle publiee par erreur dans le build public.
Hiérarchie inclusive¶
Le système de classification est cumulatif vers le bas : chaque niveau inclut tous les niveaux inférieurs. Un utilisateur ayant accès au niveau confidentiel voit egalement toutes les pages interne et public.
| Niveau cible | Pages incluses dans le build |
|---|---|
restreint | public + interne + confidentiel + restreint |
confidentiel | public + interne + confidentiel |
interne | public + interne |
public | public uniquement |
Cette hiérarchie est implementee via un mapping numerique interne au plugin :
| Valeur textuelle | Rang numerique |
|---|---|
public | 0 |
interne | 1 |
confidentiel | 2 |
restreint | 3 |
Le filtre inclut une page si et seulement si rang(classification_page) <= rang(niveau_cible).
Plugin MkDocs classification-filter¶
Rôle du plugin¶
Le plugin classification-filter est un hook MkDocs écrit en Python. Il s'intercale dans le pipeline de build MkDocs et opère en trois phases successives :
| Hook MkDocs | Action effectuee |
|---|---|
on_files | Parcourt l'ensemble des fichiers source, lit la cle classification du front matter et exclut du build toute page dont le rang depasse le niveau cible |
on_nav | Supprime les entrees de navigation qui pointent vers des pages exclues, puis efface les sections devenues vides pour eviter les nœuds orphelins dans le menu |
on_env | Injecte la variable classification_level dans le contexte Jinja2, ce qui permet aux templates d'afficher un badge visuel indiquant le niveau de build courant |
Configuration¶
Le niveau cible est fourni via la variable d'environnement MKDOCS_CLASSIFICATION au moment du build. Le plugin lit cette variable au demarrage et valide sa valeur avant d'entreprendre quoi que ce soit :
# mkdocs.yml
plugins:
- classification-filter:
env_var: MKDOCS_CLASSIFICATION # nom de la variable d'environnement
default_classification: public # valeur appliquee si front matter absent
fail_on_unknown: true # echec immediat si valeur non reconnue
# Exemple d'invocation pour le build "interne"
MKDOCS_CLASSIFICATION=interne mkdocs build --site-dir /var/www/docs/interne
Validation stricte¶
Au demarrage du plugin, la valeur de MKDOCS_CLASSIFICATION est confrontee a la liste des valeurs autorisees. Si la valeur est absente ou inconnue, le plugin leve une exception et interrompt le build avant de traiter le moindre fichier. Ce comportement garantit qu'aucun build ne se produit dans un état ambigu.
Badge de niveau dans les templates
La variable classification_level injectee par on_env peut etre utilisee dans un template Material pour afficher un bandeau colore en haut de chaque page, rappelant a l'utilisateur le niveau de classification du build qu'il consulte. Ce bandeau est purement visuel et ne constitue pas un contrôle d'accès.
Pipeline CI multi-build¶
Les quatre jobs paralleles¶
Le pipeline Gitea Actions declenche quatre jobs indépendants à chaque push sur la branche principale. Ils s'exécutent en parallele et deployent chacun leur build dans le répertoire correspondant.
| Nom du job | Variable d'environnement | Répertoire de destination |
|---|---|---|
build-public | MKDOCS_CLASSIFICATION=public | /var/www/docs/public/ |
build-interne | MKDOCS_CLASSIFICATION=interne | /var/www/docs/interne/ |
build-confidentiel | MKDOCS_CLASSIFICATION=confidentiel | /var/www/docs/confidentiel/ |
build-restreint | MKDOCS_CLASSIFICATION=restreint | /var/www/docs/restreint/ |
graph LR
Push["git push"] --> P["build-public"]
Push --> I["build-interne"]
Push --> C["build-confidentiel"]
Push --> R["build-restreint"]
P --> Deploy["Deploy /var/www/docs/"]
I --> Deploy
C --> Deploy
R --> Deploy Structure du workflow¶
# .gitea/workflows/docs.yml
name: Build documentation
on:
push:
branches: [main]
jobs:
build-public:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install mkdocs-material mkdocs-classification-filter
- run: mkdocs build --site-dir dist/public
env:
MKDOCS_CLASSIFICATION: public
- run: rsync -a dist/public/ docs@server:/var/www/docs/public/
# build-interne, build-confidentiel, build-restreint suivent la meme structure
# avec MKDOCS_CLASSIFICATION et le repertoire de destination adaptes.
Optimiser avec le cache pip
Ajouter un step de cache pour les dependances Python reduit le temps de build de chaque job de 30 a 60 secondes. Les quatre jobs beneficient du même cache car ils installent les mêmes packages.
Reverse proxy et authentification¶
Flux de la requête¶
À chaque requête entrante, le proxy execute la sequence suivante avant de servir la moindre ressource statique :
sequenceDiagram
participant U as Utilisateur
participant P as Proxy (Caddy)
participant K as Keycloak
participant S as Site statique
U->>P: GET /docs/procedure-deploiement
alt Pas de token
P->>U: 200 OK (build public, sans redirect SSO)
else Token present
P->>K: Validation du token (introspection OIDC)
K-->>P: Claims : { doc_classification: "interne" }
P->>P: Extraire claim doc_classification
P->>P: Mapper claim → repertoire de build
P->>S: Servir depuis /var/www/docs/interne/
S-->>U: Page HTML (build interne)
end Mappage claim vers build¶
La claim OIDC doc_classification est positionnee par Keycloak en fonction des groupes d'appartenance de l'utilisateur :
| Claim / groupe Keycloak | Build servi | Répertoire |
|---|---|---|
| Absent (non authentifie) | public | /var/www/docs/public/ |
doc-interne | interne | /var/www/docs/interne/ |
doc-confidentiel | confidentiel | /var/www/docs/confidentiel/ |
doc-restreint | restreint | /var/www/docs/restreint/ |
La hiérarchie inclusive s'applique ici aussi : un utilisateur membre de doc-confidentiel reçoit le build confidentiel, qui contient déjà toutes les pages interne et public.
Cas du contenu public¶
Le contenu public est servi directement, sans redirect vers le SSO. Un utilisateur anonyme qui accede a docs.company.io reçoit le build public sans avoir a s'authentifier. Ce choix est delibere : imposer une authentification pour du contenu public degraderait l'expérience et bloquerait les accès depuis des outils automatises (robots d'indexation interne, scripts de vérification).
URL transparente¶
L'utilisateur voit un unique domaine (docs.company.io). Le routage vers le bon répertoire de build est entierement interne au proxy. Les URLs des pages sont identiques quel que soit le niveau d'accès : la page /procedure-deploiement/ existe dans les quatre builds, mais son contenu peut differ selon les sections filtrees.
Configuration Caddy avec forward_auth¶
# Caddyfile
docs.company.io {
@authenticated {
header Authorization *
}
handle @authenticated {
forward_auth keycloak:8080 {
uri /realms/company/protocol/openid-connect/userinfo
copy_headers X-Doc-Classification
}
# Routage selon le header extrait par forward_auth
@restreint header X-Doc-Classification doc-restreint
@confidentiel header X-Doc-Classification doc-confidentiel
@interne header X-Doc-Classification doc-interne
handle @restreint { root * /var/www/docs/restreint ; file_server }
handle @confidentiel { root * /var/www/docs/confidentiel ; file_server }
handle @interne { root * /var/www/docs/interne ; file_server }
handle { root * /var/www/docs/public ; file_server }
}
# Pas de token : build public sans authentification
handle {
root * /var/www/docs/public
file_server
}
}
Header de routage
Keycloak peut etre configure pour inclure les groupes de l'utilisateur dans le token via un mapper de type Group Membership. Le plugin Caddy forward_auth extrait ce header et le transmet au bloc de routage.
Dimensionnement¶
Ressources du serveur de documentation¶
La documentation statique est extremement légère en termes de ressources. Le goulot d'étranglement typique est le débit réseau lors des déploiements, pas la charge CPU ou mémoire.
| Composant | Petit (< 50 utilisateurs) | Moyen (50-500) | Grand (500+) |
|---|---|---|---|
| CPU (serveur docs) | 1 vCPU | 2 vCPU | 2-4 vCPU |
| RAM (serveur docs) | 512 Mo | 1 Go | 2 Go |
| Disque (4 builds) | 1 Go SSD | 5 Go SSD | 20 Go SSD |
| CPU (Keycloak) | 2 vCPU | 2 vCPU | 4 vCPU |
| RAM (Keycloak) | 1 Go | 2 Go | 4 Go |
| CPU (Caddy) | 1 vCPU | 1 vCPU | 2 vCPU |
| RAM (Caddy) | 128 Mo | 256 Mo | 512 Mo |
Estimation de la taille des builds¶
La taille d'un build MkDocs Material depend principalement du nombre de pages et des assets (images, schemas). Pour une documentation de taille moyenne :
| Corpus | Taille d'un build | Taille des 4 builds |
|---|---|---|
| 50 pages, peu d'images | ~15 Mo | ~60 Mo |
| 200 pages, schemas Mermaid | ~80 Mo | ~320 Mo |
| 500 pages, nombreuses images | ~500 Mo | ~2 Go |
Les builds partagent les assets
Les fichiers statiques (CSS, JS du theme Material) sont identiques dans les quatre builds. Une optimisation possible consiste à les servir depuis un répertoire commun et a ne versionner que les HTML dans les quatre sous-répertoires, reduisant l'espace disque de 40 a 60 %.
Synthese¶
L'architecture de la plateforme documentaire repose sur une separation nette des responsabilites : les sources Markdown restent uniques et non dupliquees, le plugin de filtrage opère a la compilation, et le contrôle d'accès est entierement delegue au proxy et a Keycloak. La documentation statique ne contient aucune logique d'autorisation a l'execution : si un fichier se trouve dans le répertoire servi, il est accessible. La sécurité repose donc entierement sur la rigueur du pipeline de build et sur la configuration du proxy. Le chapitre suivant detaille l'installation et la configuration pas a pas de chacun de ces composants.