Supply chain
Durcir les AppProject Argo CD : le contrôle multi-tenant que tout le monde laisse ouvert
Analyse DevSecOps : neutraliser le projet default, restreindre sourceRepos et destinations, whitelister les kinds, et prouver le RBAC projet avant qu'un auditeur ne le fasse.
Dans la majorité des clusters que j’audite, Argo CD est en place, les Applications se synchronisent, et le projet default est encore celui d’installation : sourceRepos: ['*'], destinations: server '*', namespace '*', clusterResourceWhitelist: group '*', kind '*'. Autrement dit, n’importe quelle Application peut déployer n’importe quoi, depuis n’importe quel dépôt, vers n’importe quel cluster.
Ce n’est pas un oubli cosmétique. L’AppProject est la frontière multi-tenant d’Argo CD : sources Git autorisées, clusters/namespaces cibles, kinds Kubernetes permis, rôles projet. Sans lui, le RBAC global d’Argo CD (SSO, argocd-rbac-cm) ne protège que l’UI — pas ce qu’une Application a le droit de syncer.
Cet article décrit le durcissement tel que je le livre en mission DevSecOps : menace → contrôles AppProject → artefacts YAML → preuves pour un auditeur. Pas de théorie, le manifeste qui remplace le default permissif.
La menace : ce que permet un projet default ouvert
Trois scénarios reviennent en audit.
Escalade via le namespace Argo CD. La doc officielle est explicite : si un projet autorise le déploiement dans le namespace où Argo CD est installé, les Applications de ce projet ont un accès de niveau admin de fait. Un contributeur qui pousse un manifeste créant un Application (ou un AppProject, un ConfigMap argocd-rbac-cm, un Secret de cluster) dans ce namespace prend le contrôle du GitOps. Le projet default avec namespace: '*' autorise exactement ça.
Sync depuis un dépôt non maîtrisé. Avec sourceRepos: ['*'], une Application peut pointer vers un fork public, un dépôt personnel, ou un registry Git compromis. Le contrôle d’accès GitHub (CODEOWNERS, branch protection) ne s’applique plus : Argo CD tire ce que l’Application déclare.
Kinds cluster-scopés non filtrés. Un ClusterRole / ClusterRoleBinding créé par une Application « métier » donne des droits hors namespace. Les ressources cluster-scopées sont refusées par défaut sauf si une whitelist (ou l’historique */* du default) les autorise — d’où l’importance de ne jamais laisser group: '*', kind: '*'.
Le contrôle contre ces trois scénarios est le même : neutraliser default, créer un AppProject par équipe (ou par environnement), et rendre chaque frontière explicite.
Étape 0 : neutraliser le projet default
Avant d’ajouter des projets métier, fermer la porte grande ouverte. Un AppProject avec des listes vides et un blacklist total sur les ressources namespacées ne laisse plus aucune Application « orpheline » se synchroniser.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: default
namespace: argocd
spec:
description: "Neutralise — aucune Application ne doit rester sur default"
sourceRepos: []
destinations: []
sourceNamespaces: []
clusterResourceWhitelist: []
namespaceResourceBlacklist:
- group: "*"
kind: "*"
Ensuite, migrer chaque Application existante vers un projet nommé (spec.project: team-platform-prod). Tant qu’une Application référence encore default, le sync échouera — c’est voulu.
Le modèle cible : un projet par équipe et par environnement
Pour une plateforme partagée et deux ou trois équipes produit, je sépare au minimum :
| Projet | Qui sync | Sources | Destinations |
|---|---|---|---|
platform-prod | platform / SRE | repos org/platform-* | cluster prod, ns platform-* uniquement |
team-billing-prod | équipe billing | org/billing-* | cluster prod, ns billing |
team-billing-dev | équipe billing | org/billing-* | cluster dev, ns billing-* |
Ne jamais mélanger prod et non-prod dans le même AppProject : un sync accidentel (mauvais destination, mauvaise ApplicationSet) doit être bloqué au niveau projet, pas au review humain.
Artefact : AppProject durci pour une équipe produit
Exemple pour une équipe billing en production. Adaptez l’org Git, le serveur de cluster et la liste des kinds à votre stack (Helm, Kustomize, CRDs opérateurs).
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-billing-prod
namespace: argocd
labels:
team: billing
env: prod
spec:
description: "Billing — prod uniquement. Pas d'accès au ns argocd."
# Repos autorisés : pattern org, pas de wildcard global
sourceRepos:
- "https://github.com/acme-corp/billing-*"
- "https://github.com/acme-corp/shared-helm-charts"
# Où les Applications de ce projet peuvent vivre (apps-in-any-ns)
# Ne jamais y mettre "argocd" pour un projet métier
sourceNamespaces:
- "billing-gitops"
destinations:
- name: prod-eks
namespace: billing
# server: "https://...." # alternative si vous référencez par URL
- name: prod-eks
namespace: billing-jobs
# Cluster-scopé : deny by default. Ici, aucun kind cluster autorisé.
# Si l'équipe a vraiment besoin de créer son Namespace, restreindre le name :
# clusterResourceWhitelist:
# - group: ""
# kind: Namespace
# name: billing
clusterResourceWhitelist: []
# Namespacé : allow-list explicite (pas de blacklist seule)
namespaceResourceWhitelist:
- group: "apps"
kind: Deployment
- group: "apps"
kind: StatefulSet
- group: "batch"
kind: Job
- group: "batch"
kind: CronJob
- group: ""
kind: Service
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: ""
kind: ServiceAccount
- group: "networking.k8s.io"
kind: NetworkPolicy
- group: "networking.k8s.io"
kind: Ingress
- group: "policy"
kind: PodDisruptionBudget
- group: "autoscaling"
kind: HorizontalPodAutoscaler
# Ajoutez vos CRDs applicatifs un par un (ExternalSecret, etc.)
# Interdit même si un jour la whitelist s'élargit
namespaceResourceBlacklist:
- group: "rbac.authorization.k8s.io"
kind: Role
- group: "rbac.authorization.k8s.io"
kind: RoleBinding
roles:
- name: developer
description: "Sync et lecture — pas de delete Application"
groups:
- "acme-corp:billing-devs"
policies:
- "p, proj:team-billing-prod:developer, applications, get, team-billing-prod/*, allow"
- "p, proj:team-billing-prod:developer, applications, sync, team-billing-prod/*, allow"
- "p, proj:team-billing-prod:developer, applications, action/*, team-billing-prod/*, allow"
- "p, proj:team-billing-prod:developer, logs, get, team-billing-prod/*, allow"
- "p, proj:team-billing-prod:developer, exec, create, team-billing-prod/*, deny"
- name: lead
description: "Create/update/delete Applications du projet"
groups:
- "acme-corp:billing-leads"
policies:
- "p, proj:team-billing-prod:lead, applications, *, team-billing-prod/*, allow"
- "p, proj:team-billing-prod:lead, repositories, get, team-billing-prod/*, allow"
Points non négociables dans ce manifeste :
sourceReposnominatifs — le*global est interdit ; le patternorg/billing-*s’arrête au/suivant (comportement Argo CD documenté).- Pas de destination
argocd— et pas denamespace: '*'en prod. clusterResourceWhitelistvide — une équipe produit n’a en général pas besoin de CRDs cluster ou de ClusterRoles.- Rôles projet liés aux groupes SSO — le RBAC global ne donne que
p, role:readonly, applications, get, */*, allow; le droit de sync est porté par le projet.
RBAC global et SSO : le complémentaire obligatoire
L’AppProject ne remplace pas le durcissement de l’instance :
- Désactiver le compte
adminlocal une fois le SSO (OIDC / SAML) stable (admin.enabled: falsedansargocd-cm, ou équivalent Helm). - Mapper les groupes IdP dans
argocd-rbac-cmvers des rôles sansapplications, *, */*, allowpour les équipes métier. - Restreindre qui peut créer des
AppProject(rôle admin Argo CD uniquement). - Activer
applications.namespaces/--application-namespacesseulement pour les namespaces GitOps dédiés, jamais pour des namespaces applicatifs contrôlés par les équipes sans revue.
La doc Applications in any namespace le rappelle : ne jamais ajouter des namespaces utilisateur dans sourceNamespaces d’un projet privilégié.
Sync windows et orphelins
Deux contrôles souvent oubliés, faciles à ajouter sur le même AppProject :
spec:
syncWindows:
- kind: allow
schedule: "0 8-18 * * 1-5"
duration: 10h
applications:
- "*"
timeZone: "Europe/Paris"
- kind: deny
schedule: "0 0 * * 6,0"
duration: 24h
applications:
- "*"
manualSync: true # sync manuel possible en urgence
Et côté Application, orphanedResources.warn: true (voire ignore ciblé) pour détecter les objets créés hors Git dans le namespace destination — utile en preuve d’audit « tout ce qui tourne est déclaré ».
Preuve pour l’auditeur
Ce qu’on produit en fin de mission, réutilisable tel quel :
# Inventaire des projets et de leurs frontières
argocd proj list -o wide
argocd proj get team-billing-prod -o yaml
# Aucune Application ne doit rester sur default
argocd app list -o wide | awk '$2=="default" {print}'
# Vérifier qu'aucune destination n'inclut le ns argocd (hors projets admin)
kubectl -n argocd get appprojects -o json \
| jq -r '.items[] | select(.metadata.name!="default")
| .metadata.name as $p
| .spec.destinations[]?
| select(.namespace=="argocd" or .namespace=="*")
| "\($p) → \(.server // .name)/\(.namespace)"'
Attendu : zéro ligne sur le filtre destinations argocd/*, zéro Application sur default, et un export YAML des AppProjects versionné dans le dépôt GitOps de la plateforme (app-of-apps).
Checklist de reprise en 1 journée
- Exporter l’état actuel (
argocd proj list,app list). - Appliquer le
defaultneutralisé. - Créer les AppProjects par équipe/env avec whitelists.
- Migrer
spec.projectdes Applications + ApplicationSets. - Brancher les groupes SSO sur les
rolesprojet. - Désactiver l’admin local.
- Archiver les commandes de preuve ci-dessus dans le dossier audit.
Conclusion
Le durcissement Argo CD ne commence pas par les plugins ou le vaulting des secrets : il commence par l’AppProject. Tant que default reste permissif, le reste de la posture GitOps est cosmétique. Si vous voulez faire passer ce sujet en revue structurée (RBAC, AppProject, secrets, admission), l’offre Sécurité Supply Chain & CI/CD couvre exactement ce périmètre en deux semaines de cadrage et de remédiation priorisée.
Sources : Argo CD Projects, Declarative setup — AppProject, Applications in any namespace, AWS EKS — Working with Argo CD Projects.
Cet article vous a été utile ? Partagez-le.
Aller plus loin
Un sujet, une mission, une question ?
Distribuée accompagne des PME exigeantes sur la sécurité cloud, la supply chain CI/CD et la sécurité IA.
Réserver 15 min