Distribuée
Cloud & AI Security
← Tous les insights

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.

· 7 min · #argocd#appproject#gitops#kubernetes#rbac#supply-chain#hardening

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.

Quatre frontières à verrouiller sur un AppProject Argo CD

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 :

ProjetQui syncSourcesDestinations
platform-prodplatform / SRErepos org/platform-*cluster prod, ns platform-* uniquement
team-billing-prodéquipe billingorg/billing-*cluster prod, ns billing
team-billing-devéquipe billingorg/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 :

  1. sourceRepos nominatifs — le * global est interdit ; le pattern org/billing-* s’arrête au / suivant (comportement Argo CD documenté).
  2. Pas de destination argocd — et pas de namespace: '*' en prod.
  3. clusterResourceWhitelist vide — une équipe produit n’a en général pas besoin de CRDs cluster ou de ClusterRoles.
  4. 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 admin local une fois le SSO (OIDC / SAML) stable (admin.enabled: false dans argocd-cm, ou équivalent Helm).
  • Mapper les groupes IdP dans argocd-rbac-cm vers des rôles sans applications, *, */*, allow pour les équipes métier.
  • Restreindre qui peut créer des AppProject (rôle admin Argo CD uniquement).
  • Activer applications.namespaces / --application-namespaces seulement 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

  1. Exporter l’état actuel (argocd proj list, app list).
  2. Appliquer le default neutralisé.
  3. Créer les AppProjects par équipe/env avec whitelists.
  4. Migrer spec.project des Applications + ApplicationSets.
  5. Brancher les groupes SSO sur les roles projet.
  6. Désactiver l’admin local.
  7. 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