Aller au contenu
Sécuriser Argo CD

Sécuriser Argo CD

300 Concevoir ⏱ 1 h 10 argocdkubernetesgitopsopenid-connect

À la fin, vous saurez

  • Identifier ce qu'un attaquant obtient en compromettant Argo CD, et par quelles voies
  • Retirer tous les droits du projet default et cloisonner les équipes par AppProject
  • Écrire une politique RBAC sans droits par défaut, fondée sur l'identité SSO
  • Configurer la connexion OIDC avec Google et désactiver le compte admin
  • Désactiver ou restreindre les fonctions à risque et suivre les avis de sécurité

Prérequis

Testé avec argocd 3.5.3 , vérifié le 2 octobre 2026

Pourquoi

Faisons le compte de ce qu'Argo CD détient après les leçons précédentes : un identifiant d'écriture sur chaque cluster qu'il pilote, des identifiants de lecture sur les dépôts privés, la possibilité d'appliquer n'importe quel manifeste, et une interface web exposée aux équipes. Compromettre Argo CD, c'est compromettre tout ce qu'il déploie. Et il n'est pas besoin d'une faille exotique : un développeur qui peut créer une Application dans le projet default peut déployer dans le namespace kube-system d'un cluster de production.

Le modèle GitOps déplace la frontière de sécurité : ce n'est plus « qui a accès au cluster », c'est « qui peut faire en sorte qu'Argo CD applique quelque chose ». Cette leçon ferme les portes une à une.

Les concepts

Les voies d'accès

    flowchart LR
  U[Utilisateurs<br/>interface, CLI, API] -->|RBAC Argo CD| S[argocd-server]
  G[Dépôts Git] -->|droits d'écriture Git| R[repo-server]
  K[Accès kubectl<br/>au namespace argocd] -->|RBAC Kubernetes| C[Applications, AppProjects,<br/>secrets de clusters]
  S --> C
  R --> C
  C --> X[Clusters cibles]
  

Trois voies mènent aux clusters cibles, et chacune a ses propres contrôles :

  1. L'API d'Argo CD (interface, CLI) : contrôlée par la politique RBAC d'Argo CD et par les AppProjects.
  2. Les dépôts Git : quiconque peut pousser sur une branche suivie par Argo CD déploie. Protection de branche et relecture obligatoire, chez votre forge.
  3. L'API Kubernetes du cluster d'Argo CD : quiconque peut créer une Application ou modifier un AppProject dans le namespace argocd contourne totalement le RBAC d'Argo CD. Le namespace argocd doit être réservé aux administrateurs de la plateforme.

Projets et rôles

  • Un AppProject borne ce que les Applications qui lui appartiennent peuvent faire : quels dépôts sources, quelles destinations (cluster et namespace), quels types de ressources. Il porte aussi les fenêtres de synchronisation (leçon 7) et des rôles de projet.
  • La politique RBAC (argocd-rbac-cm) dit qui peut faire quoi via l'API d'Argo CD : voir, créer, synchroniser, supprimer des applications, lire les journaux, ouvrir un terminal...

Les deux se complètent : le RBAC autorise un développeur à créer des Applications dans le projet signalements ; le projet garantit que ces Applications ne pourront déployer que depuis les dépôts de Signalements, dans ses namespaces.

En pratique

Vider le projet default

Toute Application sans projet explicite appartient au projet default, créé à l'installation avec les droits les plus larges : tous les dépôts, toutes les destinations, tous les types de ressources, y compris de portée cluster. On ne peut pas le supprimer, mais on peut le vider, comme le recommande la documentation :

# Le projet default ne permet plus rien : chaque application doit choisir un vrai projet.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: default
  namespace: argocd
spec:
  sourceRepos: []
  sourceNamespaces: []
  destinations: []
  namespaceResourceBlacklist:
    - group: '*'
      kind: '*'

Attention à l'ordre : toutes les Applications du projet default sont refusées dès l'application de ce manifeste. Créez d'abord les projets, déplacez-y les Applications (spec.project), puis videz default.

Un projet par périmètre

Pour Signalements, un projet borne les sources, les destinations et les types de ressources :

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: signalements
  namespace: argocd
spec:
  description: Application Signalements, toutes ses instances
  sourceRepos:
    - http://forgejo.forge.svc.cluster.local:3000/plateforme/signalements-deploiement.git
  destinations:
    - name: in-cluster
      namespace: signalements-recette
    - name: production
      namespace: signalements-production
    - name: in-cluster
      namespace: 'apercu-*'
  # Aucune ressource de portée cluster : ni namespace, ni ClusterRole, ni CRD.
  clusterResourceWhitelist: []
  # Dans les namespaces, tout sauf ce qui permettrait de sortir du cadre.
  namespaceResourceBlacklist:
    - group: ''
      kind: ResourceQuota
    - group: ''
      kind: LimitRange
    - group: networking.k8s.io
      kind: NetworkPolicy
  roles:
    - name: deploiement-ci
      description: Synchronisation depuis la CI, rien d'autre
      policies:
        - p, proj:signalements:deploiement-ci, applications, sync, signalements/*, allow
        - p, proj:signalements:deploiement-ci, applications, get, signalements/*, allow

Quelques choix à expliquer :

  • Un seul projet pour les deux environnements : la leçon 7 avait créé un projet production pour porter les fenêtres de synchronisation. On peut aussi, comme ici, regrouper une application dans un projet unique et y placer les fenêtres, limitées à la production par clusters: [production]. Le découpage par application ou par environnement est discuté plus bas.

  • clusterResourceWhitelist: [] : une application de Signalements ne peut créer aucune ressource de portée cluster. Les namespaces sont créés par l'équipe plateforme (ou par une application du projet plateforme), ce qui rend CreateNamespace=true inutilisable ici : c'est voulu.

  • La liste noire : les quotas, limites et politiques réseau d'un namespace sont posés par l'équipe plateforme. Une équipe applicative qui pourrait les modifier lèverait ses propres garde-fous.

  • Les destinations : chaque couple cluster et namespace est listé ; un joker (apercu-*) couvre les environnements d'aperçu de la leçon 6.

  • Le rôle de projet deploiement-ci : une CI qui doit déclencher une synchronisation reçoit un jeton limité à ce projet, créé par argocd proj role create-token signalements deploiement-ci --expires-in 720h. Donnez une date d'expiration : un jeton sans expiration reste valide jusqu'à sa révocation.

Le projet se vérifie aussi côté ApplicationSet : un modèle dont le champ project est paramétrable pourrait choisir un projet plus permissif (leçon 6). Écrivez-le en dur.

Une politique RBAC sans droits par défaut

La politique RBAC vit dans le ConfigMap argocd-rbac-cm. Sa syntaxe vient de Casbin : des lignes p (une permission) et des lignes g (une appartenance à un rôle) :

p, <sujet>, <ressource>, <action>, <objet>, <effet>
g, <utilisateur ou groupe>, <rôle>
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
  labels:
    app.kubernetes.io/part-of: argocd
data:
  # Aucun droit pour un utilisateur simplement authentifié.
  policy.default: ''
  # L'identité vient de l'adresse électronique (Google ne fournit pas de groupes en OIDC).
  scopes: '[email]'
  policy.csv: |
    # L'équipe plateforme administre tout.
    g, plateforme@lyneko.example, role:admin

    # Les développeurs de Signalements : voir, synchroniser, lire les journaux, dans leur projet.
    p, role:dev-signalements, applications, get, signalements/*, allow
    p, role:dev-signalements, applications, sync, signalements/*, allow
    p, role:dev-signalements, applications, action/*, signalements/*, allow
    p, role:dev-signalements, logs, get, signalements/*, allow
    p, role:dev-signalements, projects, get, signalements, allow
    # Garde-fou : aucun droit delete n'est accordé, mais ce deny bloquera aussi un allow ajouté plus tard.
    p, role:dev-signalements, applications, delete, signalements/signalements-production, deny
    g, dev1@lyneko.example, role:dev-signalements
    g, dev2@lyneko.example, role:dev-signalements

Les points qui comptent :

  • policy.default est accordé à tout utilisateur authentifié, et la documentation précise qu'aucune règle deny ne peut le retirer. Une valeur courante dans les exemples, role:readonly, donne à tout le monde la lecture de toutes les applications, de tous les projets, des dépôts et des clusters. Laissez-le vide et accordez explicitement.
  • Le sujet est, avec OIDC, la valeur des revendications listées dans scopes, ici l'adresse électronique. Avec un fournisseur qui fournit des groupes, préférez les groupes : on gère alors les droits dans l'annuaire, pas dans un ConfigMap.
  • Les objets des applications ont la forme <projet>/<application> : le RBAC et les projets se combinent naturellement.
  • Depuis la version 3.0, update et delete sur une application ne s'étendent plus à ses ressources : supprimer un pod d'une application demande delete/*/Pod/*/*, explicitement.
  • Ne donnez jamais override à la légère : il permet de synchroniser des manifestes locaux arbitraires à la place de Git. Depuis la 3.3, application.sync.requireOverridePrivilegeForRevisionSync: "true" dans argocd-cm fait aussi exiger override pour synchroniser une révision différente de celle de l'Application ; la documentation recommande de l'activer.

On vérifie une politique avant de l'appliquer, sans serveur, avec la CLI :

argocd admin settings rbac validate --policy-file politique.csv
argocd admin settings rbac can dev1@lyneko.example delete applications 'signalements/signalements-production' \
  --policy-file politique.csv

Le SSO avec Google, sans Dex

Argo CD peut déléguer l'authentification à Dex (un fournisseur d'identité embarqué) ou parler directement à un fournisseur OIDC. Avec Google, la connexion OIDC directe suffit si l'on n'a pas besoin des groupes, que Google ne fournit pas dans son jeton OIDC (il faut alors Dex et un compte de service pour lire les groupes Google Workspace).

# argocd-cm (extrait)
data:
  url: https://argocd.lyneko.example
  oidc.config: |
    name: Google
    issuer: https://accounts.google.com
    clientID: <identifiant client>.apps.googleusercontent.com
    clientSecret: $argocd-google-oidc:clientSecret
    requestedScopes: ["openid", "profile", "email"]
  # Plus de compte local admin une fois le SSO en place.
  admin.enabled: "false"
  • L'adresse de retour à déclarer dans la console Google est https://argocd.lyneko.example/auth/callback (avec Dex, ce serait /api/dex/callback).
  • Le secret client vient du Secret argocd-google-oidc, produit par External Secrets avec l'étiquette app.kubernetes.io/part-of: argocd (leçon 8).
  • Qui peut se connecter ? N'importe quel compte Google peut s'authentifier auprès d'un client OAuth « externe ». Avec policy.default vide, un inconnu n'obtient aucun droit, mais déclarez le client comme interne dans l'écran de consentement OAuth : seuls les comptes de votre organisation Google Workspace peuvent alors se connecter.
  • Le déploiement de Dex devient inutile : on le retire (dans le chart Helm, dex.enabled: false), c'est un composant de moins à exposer et à mettre à jour.

Désactiver le compte admin

Le compte admin est un superutilisateur local, protégé par un simple mot de passe, sans second facteur. Une fois le SSO en place et un administrateur SSO vérifié (connectez-vous et testez une action d'administration avant), désactivez-le par admin.enabled: "false" et supprimez le Secret argocd-initial-admin-secret s'il existe encore. Gardez une procédure de secours documentée : un administrateur du cluster peut toujours réactiver le compte en modifiant argocd-cm.

Limiter les fonctions à risque

FonctionRisqueRéglage
Terminal web (exec)un shell dans les pods depuis le navigateurdésactivé par défaut (exec.enabled) ; si activé, exec, create à très peu de monde
Accès anonymepolicy.default sans authentificationdésactivé par défaut (users.anonymous.enabled) ; ne pas l'activer
overridesynchroniser autre chose que Gità personne, et requireOverridePrivilegeForRevisionSync
Comptes locaux avec apiKeyjetons longs, hors SSOpréférer les rôles de projet à jetons expirants
ApplicationSetcréer des Applications dans n'importe quel projetcréation réservée aux administrateurs (leçon 6)

Sous le capot

À chaque requête, argocd-server vérifie le jeton de session (signé avec la clé de argocd-secret, ou un jeton OIDC vérifié auprès de l'émetteur), en extrait les revendications de scopes, puis évalue la politique Casbin construite à partir de la politique intégrée, de policy.csv et des rôles de tous les projets. Le projet intervient ensuite au moment de la réconciliation : le contrôleur refuse d'appliquer une ressource dont le type ou la destination ne sont pas permis, et l'Application affiche une erreur de comparaison ou de synchronisation. C'est ce qui rend le projet efficace même contre une Application créée directement par kubectl, hors de l'API d'Argo CD.

Pièges courants

policy.default: role:readonly « parce que c'est pratique ». Tout utilisateur authentifié lit tout, et la lecture a déjà suffi à extraire des secrets (voir la section suivante).

Un utilisateur SSO n'a aucun droit. Le sujet ne correspond pas : vérifiez scopes (l'adresse électronique est-elle demandée et évaluée ?) et la casse de l'adresse.

application destination server '...' and namespace '...' do not match any of the allowed destinations in project '...'. La destination n'est pas listée dans le projet ; ajoutez le couple cluster et namespace.

resource :Namespace is not permitted in project. CreateNamespace=true dans un projet sans ressources de portée cluster : créez le namespace autrement.

Le compte admin désactivé avant d'avoir vérifié le SSO. Réactivez-le par kubectl dans argocd-cm, corrigez, recommencez dans le bon ordre.

Un jeton de projet qui fuit. argocd proj role delete-token le révoque ; une date d'expiration limite les dégâts si l'on n'a pas vu la fuite.

Sécurité

Argo CD est un logiciel exposé, et il a des vulnérabilités : suivez ses avis de sécurité et mettez à jour vite. Quelques avis récents montrent pourquoi le moindre privilège compte même en lecture :

  • CVE-2026-42880 (critique, mai 2026) : l'API ServerSideDiff renvoyait des Secrets Kubernetes non masqués ; un utilisateur avec la seule lecture des applications pouvait les extraire, dès lors qu'une application portait l'option de comparaison IncludeMutationWebhook=true. Corrigé en 3.2.11 et 3.3.9.
  • CVE-2026-45738 (élevée, mai 2026) : un utilisateur pouvant modifier une Application y plaçait un lien javascript: dans une annotation ; un administrateur qui cliquait exécutait le script avec sa session. Corrigé en 3.2.12, 3.3.10 et 3.4.2.
  • CVE-2025-55190 (critique, septembre 2025) : un jeton de projet limité à la gestion des applications (comme le rôle deploiement-ci plus haut) obtenait les identifiants des dépôts du projet. Corrigé en 3.1.2, 3.0.14, 2.14.16 et 2.13.9.

La leçon commune : un droit de lecture « inoffensif », accordé à tous par policy.default, a plusieurs fois suffi à obtenir des secrets. Et l'interface d'Argo CD ne devrait pas être joignable depuis Internet sans nécessité : un accès par VPN ou derrière un proxy d'authentification réduit beaucoup la surface d'attaque, notamment celle des webhooks, cibles de plusieurs dénis de service non authentifiés en 2025.

En production

Chez Lyneko, Argo CD s'authentifie directement auprès de Google en OIDC, Dex est désactivé et policy.default est vide : un compte Google qui se connecte sans être listé dans la politique ne voit rien. Les droits sont attribués par adresse électronique, faute de groupes dans le jeton Google ; c'est gérable pour une petite équipe, et c'est la raison de passer par Dex et les groupes Google Workspace le jour où l'équipe grandit.

Autre choix assumé : les applications internes de Lyneko sont protégées par un proxy d'authentification central (Pomerium), mais pas Argo CD. Sa page de connexion est joignable depuis Internet, et l'OIDC d'Argo CD est la seule barrière. La raison : derrière un proxy, tout le monde partagerait un compte local d'Argo CD, et le journal d'audit ne dirait plus qui a synchronisé ou supprimé quoi. Le prix est une surface d'attaque plus grande, compensée par policy.default vide et des mises à jour rapides ; un filtrage par adresse IP ou un accès par VPN resterait plus sûr.

Des projets par équipe ou par application ? Par équipe quand plusieurs équipes partagent Argo CD (le projet devient la frontière entre elles), par application quand une même équipe veut des garde-fous différents selon la sensibilité. Dans tous les cas, un projet plateforme, réservé aux administrateurs, porte ce qui a besoin de ressources de portée cluster (contrôleurs, CRD, namespaces).

Auditer. Les actions faites par l'API d'Argo CD (synchronisations, suppressions, modifications) apparaissent comme événements Kubernetes sur les Applications et dans les journaux d'argocd-server. Conservez ces journaux hors du cluster.

Exercices

1. Un développeur ne peut pas utiliser l'API d'Argo CD, mais il a le rôle edit sur le namespace argocd du cluster. Que peut-il faire ?

Solution

Tout : créer une Application dans un projet permissif (ou modifier un AppProject pour élargir ses destinations), et le contrôleur l'appliquera avec ses propres identifiants, sur n'importe quel cluster enregistré. Il peut aussi lire les Secrets du namespace, donc les identifiants des clusters et des dépôts. Le namespace argocd est réservé aux administrateurs de la plateforme.

2. Écrivez les lignes RBAC qui permettent à role:astreinte de synchroniser et de lancer l'action restart sur toutes les applications de tous les projets, sans pouvoir supprimer ni modifier quoi que ce soit.

Solution
p, role:astreinte, applications, get, */*, allow
p, role:astreinte, applications, sync, */*, allow
p, role:astreinte, applications, action/apps/Deployment/restart, */*, allow
p, role:astreinte, logs, get, */*, allow

Rien d'autre n'est accordé : sans règle update ni delete, ces actions sont refusées, puisque policy.default est vide.

3. Vous videz le projet default un vendredi après-midi. Lundi, plus aucune synchronisation ne fonctionne. Pourquoi, et comment l'éviter ?

Solution

Les Applications qui n'avaient pas de projet explicite appartenaient à default, qui ne permet plus aucune source ni destination : elles sont toutes refusées. Il fallait d'abord créer les projets, y déplacer chaque Application, vérifier qu'aucune ne restait dans default (argocd app list -p default), et seulement ensuite vider default.

4. Pourquoi un client OAuth Google « interne » plutôt qu'« externe », alors que policy.default est vide ?

Solution

Défense en profondeur : avec un client externe, n'importe quel compte Google peut s'authentifier. Il n'a aucun droit, mais il devient un utilisateur authentifié, et plusieurs vulnérabilités passées n'exigeaient qu'une authentification, ou que la lecture accordée par défaut. Un client interne refuse la connexion à toute personne extérieure à l'organisation Google Workspace, avant même Argo CD.

Récapitulatif

  • Compromettre Argo CD, c'est compromettre tous les clusters qu'il pilote. Trois voies y mènent : son API, les dépôts Git, le namespace argocd.
  • Videz le projet default et placez chaque application dans un AppProject qui borne sources, destinations et types de ressources.
  • policy.default vide, des droits accordés explicitement, des objets <projet>/<application>, jamais override sans raison.
  • SSO (Google en OIDC direct chez Lyneko, client interne), puis compte admin désactivé, dans cet ordre.
  • Les fonctions à risque (terminal web, accès anonyme, comptes locaux à jetons) restent désactivées ou très restreintes.
  • Suivez les avis de sécurité : la lecture seule a déjà suffi à extraire des secrets.

Pour aller plus loin

Voir ma constellation →

Sources