Aller au contenu

ADR 0002 — Connecteur MCP Kubernetes en lecture seule

Statut : accepté, exposition distante par Cloudflare Access Service Auth

Issue : #75

Décision : utiliser d'abord containers/kubernetes-mcp-server derrière une façade d'authentification, avec deux barrières indépendantes : filtrage MCP et RBAC Kubernetes minimal.

Contexte

Les agents externes doivent pouvoir diagnostiquer le cluster sans recevoir le kubeconfig administrateur. Un outil « lecture seule » reste risqué : les noms, annotations et événements peuvent contenir des instructions malveillantes ou des données privées, et certaines sous-ressources de lecture (nodes/proxy, pods/log) donnent accès à beaucoup plus d'informations que leur verbe HTTP ne le suggère.

Choix retenu pour le PoC

  • kubernetes-mcp-server v0.0.66, épinglé au digest sha256:6d650f4bd6ac303ad82713c997e73a2d001602f9bf17392c9b9a0e30e29c6423 ;
  • seul le toolset core est chargé ;
  • read_only = true, outils d'exécution et de logs désactivés ;
  • Secret, ConfigMap, ServiceAccount et RBAC refusés dans la configuration ;
  • ServiceAccount et ClusterRole par liste blanche, sans rôle Kubernetes view ;
  • aucune permission create, update, patch, delete, bind, impersonate ou escalate ;
  • aucune permission sur pods/log, pods/exec, pods/attach, nodes/proxy, services/proxy ou les tokens de ServiceAccount ;
  • aucun Ingress ; Cloudflare Tunnel sortant avec deux connecteurs et NetworkPolicy limitant l'entrée MCP à ces connecteurs, avec egress serveur limité à CoreDNS, au Service Kubernetes sur 10.43.0.1:443 et aux control-planes sur leur port 6443 ;
  • ConfigMap généré avec suffixe de hash pour garantir un rollout à chaque changement de la configuration de sécurité ;
  • limites HTTP, ressources du conteneur et contexte de sécurité restrictifs.

Le dossier apps/ovh-primary/cluster-observer est référencé par la Kustomization racine pour valider le runtime dans le cluster réel. Cette activation expose mcp.djayzone.com sans publier l'origine : le Service reste ClusterIP et Cloudflare Access exige un Service Token distinct par agent.

Modèle de menace

Menace Barrière prévue Preuve attendue avant activation
Prompt injection dans un objet ou événement Instructions serveur et traitement comme données non fiables scénario de test injectant une annotation hostile
Lecture d'un Secret ressource refusée par MCP et absente du RBAC auth can-i = no et appel MCP refusé
Commande ou mutation mode read-only, outils désactivés, RBAC sans verbes d'écriture tests négatifs create/patch/delete/exec
Accès Kubelet indirect aucune permission nodes/proxy; outils node log/stats désactivés auth can-i get nodes/proxy = no
Exfiltration par logs ou ConfigMap outils logs désactivés, ressources non autorisées appel MCP refusé et RBAC = no
Valeurs sensibles écrites directement dans une spec Pod risque résiduel documenté : get/list pods expose les variables d'environnement littérales, même sans droit sur les Secrets audit des specs avant activation, suppression des valeurs sensibles inline et tests sur les réponses MCP
Exfiltration réseau après lecture d'un objet hostile deny ingress et egress limité à DNS + API Kubernetes test réseau négatif vers Internet et les autres namespaces
Vol de l'identité MCP identité par client, expiration, rotation et révocation test de révocation sans redéploiement applicatif
Déni de service limites de corps, rate limit, timeouts et ressources bornées test de surcharge contrôlé
Contournement de GitOps aucun outil d'écriture ou de réconciliation absence des outils et permissions Flux d'écriture

Matrice outils et RBAC

Besoin Outil MCP Permission Kubernetes Décision
Pods non prêts pods_list, resources_get get/list/watch pods,pods/status autorisé
Événements récents events_list get/list/watch events autorisé, contenu non fiable
Workloads resources_list/get lecture apps et batch autorisé
Flux resources_list/get lecture des CRD Flux sélectionnées autorisé
Certificats resources_list/get lecture cert-manager et ACME autorisé
Capacité nœuds resources_list/get, nodes_top lecture nodes et metrics.k8s.io autorisé sans proxy Kubelet
Logs pods_log get pods/log refusé en V1
Kubelet nodes_log, nodes_stats_summary get nodes/proxy refusé
Configuration ressources ConfigMap/Secret/RBAC permissions correspondantes refusé
Remédiation create/update/delete/exec/Flux reconcile verbes d'écriture refusé

Authentification distante

Cloudflare Access applique une politique Service Auth. Chaque agent reçoit son couple CF-Access-Client-Id / CF-Access-Client-Secret, révocable séparément. Les valeurs restent hors Git et le token du tunnel est provisionné directement dans un Secret Kubernetes. Aucun jeton partagé permanent n'est acceptable. L'authentification de l'utilisateur MCP et l'identité Kubernetes du serveur sont deux responsabilités distinctes : le serveur conserve toujours son RBAC minimal.

Étapes suivantes

  1. rendre le PoC hors ligne et vérifier sa configuration — terminé ;
  2. lancer le workload interne et inventorier exactement tools/list ;
  3. ajouter des tests négatifs automatisés pour chaque interdiction ;
  4. tester Cloudflare Access avec les clients réels ;
  5. ajouter une supervision externe qui ne requiert pas de secret partagé.

Retour arrière

Retirer cloudflared.yaml coupe l'exposition sans publier l'origine. Révoquer le Service Token concerné coupe un seul agent ; la rotation du token de tunnel invalide les connecteurs existants.