Aller au contenu

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.