---
title: "Sécuriser Apache SkyWalking avec ZITADEL"
canonical: "https://blog.kaptus.net/space/BKAP/blog/915505167/S%C3%A9curiser%20Apache%20SkyWalking%20avec%20ZITADEL"
format: markdown
---
> Macro (toc)

# 1. Introduction

[Apache SkyWalking](https://skywalking.apache.org/) est une plateforme d'observabilité (APM) pour surveiller les applications distribuées : 

- traces,
- métriques,
- topologie de services,
- analyse des performances.

Puissante et complète, elle souffre pourtant d'une lacune notable (***A CE JOUR***) dès qu'on la déploie en production : 

> ❌ **elle n'embarque aucune gestion des utilisateurs**.

Concrètement, l'interface web de SkyWalking est ouverte. Toute personne connaissant l’URL, accède à l'intégralité des données d'observabilité :

- cartographie de l'infrastructure,
- métriques applicatives,
- traces détaillées.

Pour une plateforme qui expose par nature des informations sensibles sur le fonctionnement interne de mes systèmes, c'est un angle mort de sécurité qu'on ne peut pas laisser en l'état.

Plusieurs approches existent pour combler ce manque. 

1. La première consiste à s'appuyer sur une authentification HTTP Basic avec un fichier `.htpasswd` : mots de passe gérés à la main, pas de MFA, pas de SSO, aucune centralisation.
2. La deuxième approche consiste à **déléguer l'authentification à un fournisseur d'identité** via le protocole OpenID Connect, en plaçant un reverse proxy devant l’application `SkyWalking`.

C'est cette seconde solution que j'ai choisie pour plusieurs raisons :

- **Open source et auto-hébergeable.** `ZITADEL` est publié sous licence open source et peut être déployé sur ma propre infrastructure, sans dépendance à un service tiers ni fuite des identités vers un fournisseur externe. Les données d'authentification restent maîtrisées de bout en bout.
- **Standards ouverts.** `ZITADEL` implémente OpenID Connect et OAuth 2.0 de façon conforme, ce qui garantit l'interopérabilité avec `mod_auth_openidc`, et plus largement, avec n'importe quel `Relying Party` respectant ces standards. Aucun verrouillage propriétaire : ***le jour où l'on souhaite changer de brique, le protocole reste le même***.
- **Centralisation des identités (SSO).** Une seule source de vérité pour les comptes, réutilisable par toutes les applications de l'infrastructure. Protéger `SkyWalking` aujourd'hui, c'est poser une brique qui servira demain à sécuriser d'autres services de la même façon, avec les mêmes comptes.
- **Sécurité intégrée.** `MFA` (`TOTP`, `U2F/passkey`), politiques de mot de passe, gestion fine des autorisations par projet et par rôle, verrouillage de comptes : autant de fonctions disponibles sans développement, là où une authentification maison demanderait un effort considérable pour un résultat inférieur.
- **Séparation authentification / autorisation.** `ZITADEL` distingue clairement le fait de *prouver son identité* du fait d'*avoir le droit d'accéder* à une ressource. Cette distinction, que j’exploiterai pour verrouiller `SkyWalking` à un compte précis, permet un contrôle d'accès centralisé et auditable côté fournisseur d'identité plutôt que dispersé dans les configurations de chaque proxy.
- **Multi-tenancy natif.** L'organisation, le projet et l'application sont des concepts de premier ordre dans `ZITADEL`.

> 📝 Le choix de `ZITADEL` n'est d'ailleurs pas propre à `SkyWalking` : J’ai initialement retenu et déployé dans mon infrastructure comme fournisseur d'identité pour l’utiliser avec [NetBird](https://netbird.io/) (***projet qui a été abandonné suite à la rencontre de certains problèmes d’exploitation***).  
> 📝 Disposant déjà d'une instance `ZITADEL` opérationnelle et éprouvée, l'étendre à la protection de `SkyWalking` me semblait un bonne idée.

Nous allons protéger l'UI de SkyWalking avec :

- **Apache** en reverse proxy, jouant le rôle de `Relying Party OIDC` grâce au module `mod_auth_openidc` ;
- `ZITADEL` comme fournisseur d'identité (`OpenID Provider`), gérant l'authentification, les autorisations incluant le `MFA` ;
- le tout **sans modifier une seule ligne de SkyWalking**, qui reste inchangé derrière le proxy.

Le résultat final est une plateforme d'observabilité verrouillée par un ***SSO d'entreprise*** : 

- accès restreint à des comptes explicitement autorisés,
- authentification à deux facteurs (TOTP)
- déconnexion propre propagée jusqu'au fournisseur d'identité.

> ✅ **Une démarche menée avec l'assistance d'une IA**
> ✅ 
> ✅ *Cette solution a été conçue et mise en œuvre en binôme avec une intelligence artificielle (Claude, d'Anthropic). Le partage des rôles a été clair : *
> ✅ 
> ✅ - *l'IA a servi d'assistant technique : *
> ✅   - *explorer les pistes, *
> ✅   - *expliciter les mécanismes, *
> ✅   - *proposer les configurations,*
> ✅   - *aider à diagnostiquer les blocages ; *
> ✅ - *de mon côté :*
> ✅   - *piloter la démarche, *
> ✅   - *exécuter et tester chaque étape sur mon infrastructure réelle, *
> ✅   - *valider les résultats et trancher les choix d'architecture. *
> ✅ 
> ✅ *Le présent article rend compte de ce travail collaboratif, dont chaque configuration a été éprouvée en conditions réelles.*

# 2. Architecture de la solution

## 2.1. Principe général

Le montage repose sur un principe simple : **SkyWalking ne gère pas l'authentification, il n'a même pas à savoir qu'elle existe**. Toute la logique d'accès est déportée sur le reverse proxy, qui refuse ou laisse passer les requêtes avant qu'elles n'atteignent l'application.

Trois acteurs entrent en jeu :

- `ZITADEL` -> le fournisseur d'identité (`OpenID Provider, OP`). Il authentifie les utilisateurs, applique le `MFA` et décide qui a le droit d'accéder à l'application.
- `Apache` → le reverse proxy, qui joue le rôle de `Relying Party (RP) OIDC` via le module `mod_auth_openidc`. Il intercepte chaque requête, vérifie qu'une session valide existe, et redirige vers `ZITADEL` le cas échéant.
- `Apache SkyWalking `→ l'application protégée, composée de son `UI` et de son backend `OAP`, inchangée et accessible uniquement en local.


> Macro (drawio)

## 2.2. Le flux d'authentification

Le cheminement d'une requête, du navigateur jusqu'à l'affichage de l'interface :

1. L'utilisateur demande `https://skywalking.kpt.pw/`. Apache constate qu'aucune session valide n'existe.
2. Apache le redirige (302) vers ZITADEL pour authentification.
3. L'utilisateur s'authentifie auprès de ZITADEL (identifiant, mot de passe, puis second facteur TOTP).
4. ZITADEL le renvoie vers le *callback* d'Apache (`/oauth2callback`) avec un **code d'autorisation**.
5. Apache échange ce code contre des jetons (*ID token* et *access token*) directement auprès de ZITADEL, de serveur à serveur.
6. Apache ouvre une **session locale** (matérialisée par un cookie chiffré) et redirige l'utilisateur vers l'URL qu'il visait initialement.
7. La requête, désormais porteuse d'une session valide, est **proxifiée** vers l'UI SkyWalking en loopback (`127.0.0.1:8080`), qui renvoie l'interface.

> ℹ️ En clair : tant qu'aucune session valide n'existe, `Apache` renvoie l'utilisateur vers `ZITADEL`. Une fois l'authentification réussie, une session locale est établie sur le proxy, et les requêtes suivantes atteignent SkyWalking sans nouvelle redirection - jusqu'à l'expiration de la session.

> 📝 <u>**Un point d'architecture déterminant**</u>
> 📝 
> 📝 Ce montage fonctionne parce que **le navigateur ne dialogue qu'avec l'UI SkyWalking**. L'UI est un backend qui proxifie lui-même ses requêtes `GraphQL` vers `l'OAP` en interne : le navigateur n'appelle jamais l'`OAP` directement. Il suffit donc de protéger le **seul port de l'UI** pour verrouiller l'intégralité de l'interface.
> 📝 
> 📝 :warning: -> **l'ingestion de la télémétrie n'est pas concernée**. Les agents `SkyWalking` envoient leurs données à l'`OAP` via ses ports `gRPC/HTTP`, qui ne transitent pas par le reverse proxy. La couche d'authentification protège donc l'accès *humain* à l'interface, sans jamais interférer avec la *collecte* des données.
> 📝 
> 📝 Ces deux plans — **consultation** et **ingestion**, restent parfaitement séparés, comme l'illustre le schéma ci-dessus : le flux humain (en bleu) passe par le point de contrôle `OIDC` d'Apache, tandis que le flux de télémétrie (en orange) rejoint directement l'`OAP`, hors du périmètre d'authentification. C'est un point à intégrer dès la conception : chercher à « protéger » aussi les ports de l'OAP serait une erreur, qui casserait la remontée des données sans bénéfice de sécurité sur l'accès humain.

# 3. Verrouiller l'accès : authentification ≠ autorisation

## 3.1. Le constat qui change tout

Une fois l'authentification `OIDC` en place, j'avais un premier sentiment de satisfaction : 

- SkyWalking était protégé, plus personne n'y accédait sans passer par ZITADEL.

Sauf que « passer par `ZITADEL` » ne veut pas dire « avoir le droit d'entrer ». Avec la directive `Require valid-user`, **tout compte valide de mon organisation ZITADEL pouvait se connecter**.

Une distinction fondamentale :

- **Authentification** : prouver son identité.
- **Autorisation** : avoir le droit d'accéder à une ressource donnée.

> ℹ️ `Require valid-user` ne vérifie que la première. Il me fallait la seconde.

## 3.2. Mise en place de la restriction

O*ù* placer ce contrôle d'autorisation ?

**Première option, côté proxy.** `mod_auth_openidc` sait filtrer sur un attribut du jeton — je pouvais restreindre l'accès à une adresse e-mail précise directement dans Apache :

```apache
Require user skywalking@kaptus.fr
```

Pas l’idéal car la règle d'accès vit dans la configuration du proxy. Chaque service protégé aurait sa propre liste, dispersée dans autant de fichiers `vhost`.

**Seconde option, côté fournisseur d'identité.** Porter la restriction dans `ZITADEL` lui-même, de sorte que l'utilisateur non autorisé soit refusé **avant même** d'atteindre Apache. 

C'est cette option que j'ai retenue: 

- Centralisée,
- auditable,
- cohérente pour tous les services à venir.

## 3.3. Comment ZITADEL matérialise l'autorisation

Le modèle de `ZITADEL` sépare proprement les deux notions. L'authentification est globale à l'instance ; l'autorisation, elle, se joue au niveau du **projet**.

Le verrou tient en trois décisions :

1. **Définir un rôle** sur le projet `SkyWalking`.
2. **Activer « Only authorized users can authenticate »** sur le projet.
3. **Accorder l'autorisation** au seul compte concerné dans notre cas.

## 3.4. Le compte dédié : moindre privilège

J’ai choisi de créé un **compte dédié à SkyWalking** :

- l'accès à l'observabilité est séparé de mon identité d'administrateur ;
- si ce compte doit être partagé ou révoqué, cela n'affecte rien d'autre ;

## 3.5. Durcir l'accès : 2FA et login épuré

J'ai donc ajouté une **authentification à deux facteurs** (2FA) sur le compte dédié.

`ZITADEL` propose plusieurs seconds facteurs :

- application d'authentification (`TOTP`),
- clés `FIDO2/passkey` (Touch ID, Yubikey),
- `OTP` par e-mail ou SMS.

J'ai retenu le `TOTP` : 

- portable,
- indépendant du poste de travail,
- sans dépendance à un canal externe (contrairement aux `OTP` par `SMS` ou `e-mail`).

## 3.6. Déconnexion propre (RP-initiated logout)

Dans un montage à base de `SSO`, c'est plus subtil — et l'ignorer laisse une faille discrète.

Le problème tient à la double session. Quand un utilisateur accède à SkyWalking, **deux sessions coexistent** : 

- celle qu'Apache maintient localement (le cookie chiffré du proxy),
- celle que `ZITADEL` (la session `SSO`).

Une déconnexion naïve, qui se contenterait d'effacer le cookie d'Apache, laisserait **la session ZITADEL intacte**. 

Résultat : l'utilisateur croit s'être déconnecté, mais sa session d'identité reste ouverte.

> ✅ Se déconnecter *proprement*, c'est donc fermer les **deux** sessions.

### 3.6.1. Le mécanisme

Une URL de déconnexion est mise en place, contenant l'adresse de la page d'atterrissage souhaitée. 

Le module enchaîne alors trois actions :

1. il **détruit la session locale** d'Apache ;
2. il redirige le navigateur vers l'*end_session_endpoint* de `ZITADEL`, en transmettant l'identité de la session à fermer ;
3. `ZITADEL` **tue sa propre session** et renvoie l'utilisateur vers la page d'atterrissage prévue.

Côté `ZITADEL`, une seule contrainte : 

- l'URL de retour après déconnexion doit être **déclarée** dans l'application, au titre des *Post Logout URIs*.

> ℹ️ Comme pour les URL de redirection après login, l'IdP n'accepte de renvoyer l'utilisateur que vers une adresse qu'il connaît  (une protection contre les redirections arbitraires).

### 3.6.2. Vérifier que la déconnexion est réelle

La vérification fiable consiste à **observer l'état de session directement sur l'IdP** : 

- après déconnexion, ouvrir l'interface de `ZITADEL` dans le même navigateur.
  - Si `ZITADEL` redemande une authentification → la session SSO a bien été détruite. La déconnexion est complète.
  - Si l'utilisateur est encore connecté → seule la session locale d'Apache a sauté, et le *single logout* n'a pas fonctionné.

C'est ce test qui confirme, sans ambiguïté, que la déconnexion se propage bien jusqu'au fournisseur d'identité.

# 4. Les pièges rencontrés

Voici les quatre obstacles qui m'ont réellement coûté du temps, et ce que j'en ai tiré.

## 4.1. Le 400 Bad Request des codes d'autorisation

### **4.1.1. Symptôme.** 

Le tout premier login échouait de façon déroutante : après authentification, ZITADEL redirigeait vers le *callback*, et Apache répondait par un `400 Bad Request` sec — « Your browser sent a request that this server could not understand ». La page d'erreur venait d'Apache lui-même, pas du module `OIDC`.

### **4.1.2. Cause.** 

`ZITADEL` **chiffre ses codes d'autorisation** (ce sont des jetons JWE). Combinés au cookie d'état que le navigateur renvoie sur le *callback*, ils produisent une requête dont la taille dépasse la limite par défaut d'Apache (`LimitRequestLine` / `LimitRequestFieldSize`, 8190 octets). Apache rejette la requête au niveau du parsing `HTTP`, avant même que `mod_auth_openidc` ne la voie.

### **4.1.3. Résolution.** 

Relever ces limites. Subtilité qui pousse à l'erreur : ce sont des directives de contexte *server config* :

```apache
LimitRequestLine 65536
LimitRequestFieldSize 65536
```

> 📝 Elles ne s'appliquent pas dans un `<VirtualHost>` → il faut les poser globalement.

## 4.2. La déconnexion qui relançait une connexion

### **4.2.1. Symptôme.** 

En mettant en place la déconnexion, ma page « Vous êtes déconnecté » était systématiquement… reconnectée. Au lieu de s'afficher, elle déclenchait un nouveau cycle d'authentification.

### **4.2.2. Cause.** 

La page d'atterrissage après déconnexion se trouvait sous la protection `<Location />`. Y accéder, même juste pour l'afficher, réactivait le mécanisme `OIDC`, qui redirigeait vers `ZITADEL`.

### **4.2.3. Résolution.** 

Exempter explicitement les chemins de déconnexion de la protection `OIDC`. Un simple `Require all granted` ne suffit pas : 

- `mod_auth_openidc` intervient tôt dans le cycle de traitement, avant la phase d'autorisation.

La directive qui débloque réellement la situation est `OIDCUnAuthAction pass`, qui indique au module de laisser passer les requêtes non authentifiées au lieu de les rediriger.

```apache
<Location /loggedout.html>
    Require all granted
    OIDCUnAuthAction pass
</Location>
```

## 4.3. L'ordre des directives de proxy

### **4.3.1. Symptôme.** 

Mes URL de déconnexion partaient vers `SkyWalking`, qui répondait par sa propre page 404, au lieu d'être traitées par Apache.

### **4.3.2. Cause.** 

`ProxyPass` est évalué avant `mod_rewrite`, et **le premier motif qui correspond l'emporte**. Un `ProxyPass /` en tête de liste capturait toutes les URL avant que les exclusions spécifiques ne soient lues. J'avais bien déclaré les exclusions… mais après le `catch-all`, donc trop tard.

### **4.3.3. Résolution.** 

Ordonner rigoureusement : les exclusions (`!`) d'abord, le proxy générique en dernier.

```apache
ProxyPass /oauth2callback  !
ProxyPass /logout          !
ProxyPass /loggedout.html  !
ProxyPass /                http://127.0.0.1:8080/
```

# 5. Pour conclure

Le point de départ → une interface d'observabilité dont l’`UI` n'était pas sécurisée, exposant sans le moindre contrôle la cartographie et les métriques internes de l'infrastructure. 

Le point d'arrivée → une plateforme verrouillée par un `SSO` d'entreprise : 

- accès réservé à un compte unique et explicitement autorisé,
- authentification renforcée par un second facteur,
- session réellement fermée à la déconnexion,

et tout cela sans avoir modifié SkyWalking d'un iota.

Ce résultat ne tient pas à une technologie miracle, mais à une **succession de décisions d'architecture cohérentes** : 

- déléguer l'authentification au reverse proxy plutôt que la coder,
- porter l'autorisation sur le fournisseur d'identité plutôt que la disperser dans les proxies,
- traiter chaque réglage comme une réduction méthodique de la surface exposée :
  - MFA TOTP,
  - épuration du login,
  - déconnexion propagée.

> ✅ Au-delà de `SkyWalking`, le principal bénéfice est ailleurs :
> ✅ 
> ✅ - un **modèle éprouvé et réutilisable**.
> ✅ 
> ✅ Le fournisseur d'identité, initialement déployé pour un autre besoin, s'est révélé être le socle sur lequel adosser la sécurité d'accès de n'importe quel service interne. Chaque application sans authentification native qui viendra ensuite pourra être protégée de la même façon, avec les mêmes comptes et le même niveau d'exigence.

> ⚠️ **Un point de vigilance à assumer.** Centraliser l'authentification sur `ZITADEL` a une contrepartie : 
> ⚠️ 
> ⚠️ - le fournisseur d'identité devient une **dépendance critique du chemin d'accès**.
> ⚠️ 
> ⚠️ Si `ZITADEL` est indisponible, plus personne ne pourra se connecter à l'interface de `SkyWalking`. 
> ⚠️ 
> ⚠️ ✅ → **SkyWalking continue de tourner et la télémétrie continue d'être collectée**. 
> ⚠️ 
> ⚠️ A terme, pour un service dont dépendra l'accès à plusieurs applications, justifiera de déployer `ZITADEL` en **haute disponibilité** : 
> ⚠️ 
> ⚠️ - son architecture sans état s'y prête nativement (plusieurs instances derrière un répartiteur de charge, adossées à une base PostgreSQL répliquée).
> ⚠️ 
> ⚠️ ***Une évolution à prévoir dès que l'IdP desservira plusieurs applications critiques.***

# 6. Liens utiles

Les ressources ci-dessous complètent cet article, que ce soit pour approfondir un composant ou reproduire la démarche.

| **Lien** | **Description** |
| --- | --- |
| [Apache SkyWalking](https://skywalking.apache.org/) | Site officiel de la plateforme d'observabilité :<br>- documentation,
- téléchargements
- guides de déploiement. |
| [ZITADEL](https://zitadel.com/) | Site officiel du fournisseur d'identité open source utilisé comme OpenID Provider. |
| [Documentation ZITADEL](https://zitadel.com/docs) | Point d'entrée de la documentation :<br>- configuration des projets,
- applications,
- politiques de login
- MFA. |
| [mod_auth_openidc (dépôt GitHub)](https://github.com/OpenIDC/mod_auth_openidc) | Module Apache faisant office de `Relying Party OIDC` :<br>- code source,
- wiki
- référence des directives de configuration. |
| [Wiki mod_auth_openidc](https://github.com/OpenIDC/mod_auth_openidc/wiki) | Documentation détaillée des directives, dont la gestion du logout et les paramètres de session. |
| [ZITADEL — Feature Restrictions](https://zitadel.com/docs/guides/manage/customize/restrictions) | Restriction des langues et autres réglages d'instance évoqués dans l'article. |
| [ZITADEL — Production & Haute disponibilité](https://zitadel.com/docs/self-hosting/manage/production) | Guide de déploiement en production et mise en cluster, en lien avec la mise en garde de la conclusion. |
| [OpenID Connect](https://openid.net/developers/how-connect-works/) | Présentation du protocole sous-jacent, pour qui souhaite comprendre les mécanismes d'authentification mobilisés. |