Aller au contenu

Gouvernance des identités : SSO OIDC, RBAC par profil & audit

Objectifs pédagogiques

  • Définir des profils d'accès et appliquer la séparation des tâches sur toute la chaîne de livraison
  • Brancher ArgoCD, Grafana et OpenBao sur un fournisseur d'identité unique (SSO OIDC)
  • Traduire les profils en RBAC ArgoCD, Kubernetes et OpenBao, au moindre privilège
  • Encadrer un compte d'urgence (break-glass) sans en faire une porte dérobée
  • Activer, centraliser et exploiter les journaux d'audit, puis tester les droits automatiquement

Pourquoi gouverner les identités


Ce qui tourne mal sans gouvernance

  • Un compte de développeur hameçonné donne accès à la production
  • Des comptes partagés rendent toute enquête impossible : « admin » n'est personne
  • Des droits accordés pour une urgence ne sont jamais retirés
  • Un départ n'est pas répercuté dans tous les outils
  • Personne ne sait répondre à l'auditeur : qui a accès à quoi, et qui a fait quoi

Les principes

  • Identité nominative : chaque action est rattachée à une personne ou à un workload précis
  • Source unique : les identités humaines vivent chez un seul fournisseur, les outils s'y branchent
  • Moindre privilège : des droits par profil et par environnement, jamais « admin par défaut »
  • Séparation des tâches : celui qui écrit un changement n'est pas celui qui l'approuve
  • Cycle de vie : une arrivée ou un départ se gère à un seul endroit
  • Traçabilité : chaque accès sensible laisse une trace exploitable

Profils & séparation des tâches


Des profils, pas des personnes

Profil Staging Production Outils
Développeur lecture, synchronisation aucun accès Git, ArgoCD, Grafana
Opérateur lecture, synchronisation lecture, synchronisation ArgoCD, Grafana, OpenBao prod
Sécurité lecture lecture hors secrets Grafana, DefectDojo, audit
Auditeur lecture lecture des journaux Grafana (lecture)
Break-glass administrateur hors ligne, alerté
  • Chaque profil correspond à un groupe chez le fournisseur d'identité
  • Les droits s'attribuent au groupe, jamais à l'individu

Séparation des tâches

Écrire, approuver et déployer en production sont trois droits distincts

  • Git : branche main protégée, merge réservé aux opérateurs, approbation obligatoire par une autre personne que l'auteur
  • CODEOWNERS : les fichiers de production exigent l'approbation du profil opérateur (offres Premium et Ultimate)
  • ArgoCD : la synchronisation de l'AppProject de production est réservée au profil opérateur
  • OpenBao : seuls les opérateurs accèdent à l'instance de production
  • Kubernetes : aucun profil humain n'écrit directement dans le cluster, tout passe par Git

Le compte d'urgence (break-glass)

Le SSO, ArgoCD ou le fournisseur d'identité peuvent eux-mêmes tomber : il faut une issue de secours

  • Conservation : hors ligne, chiffrée, accès à deux personnes
  • Détection : toute utilisation déclenche une alerte immédiate
  • Justification : chaque usage donne lieu à un compte rendu
  • Rotation : identifiants renouvelés après chaque utilisation
  • Sur k3s, c'est le kubeconfig administrateur, dont le certificat appartient au groupe system:masters
  • Un compte d'urgence utilisé au quotidien n'est plus un compte d'urgence

SSO OIDC


OIDC en bref

  • Fournisseur d'identité (IdP) : authentifie l'utilisateur et émet un ID token signé
  • Client : l'application (ArgoCD, Grafana, OpenBao) enregistrée chez l'IdP avec une URL de retour
  • Claims : les informations portées par le jeton (sub, email, groupes)
  • Flux authorization code : l'utilisateur s'authentifie chez l'IdP, l'application échange un code contre les jetons
  • L'application n'a jamais le mot de passe de l'utilisateur
  • Les groupes du jeton deviennent la clé de l'autorisation dans chaque outil

GitLab comme fournisseur d'identité

  • Une application de groupe par outil (Groupe → Paramètres → Applications), avec son URL de retour
  • Les profils sont des sous-groupes : <groupe>/dev, <groupe>/ops, <groupe>/sec, <groupe>/auditeurs
  • Le claim groups_direct (groupes dont l'utilisateur est membre direct) est inclus dans l'ID token
  • Le claim groups (avec héritage) n'est disponible que dans userinfo
  • Conséquence : un outil qui ne lit que l'ID token ne voit que les appartenances directes
  • Un départ de l'entreprise ou d'un sous-groupe se répercute à la session suivante

Dex, le courtier d'identité

  • Dex se place entre les applications et un ou plusieurs fournisseurs (GitLab, LDAP, autre IdP)
  • Il normalise les identités et expose les groupes sous un claim groups homogène
  • Le connecteur GitLab demande les scopes read_user et openid
  • Son option groups filtre les groupes transmis et refuse les utilisateurs qui n'en font partie d'aucun
  • ArgoCD embarque Dex : son URL de retour est <url argocd>/api/dex/callback
  • Un Dex autonome peut servir tous les outils, y compris kubectl (en bonus)

ArgoCD : SSO GitLab par Dex

# values du chart argo-cd
configs:
  cm:
    url: http://argocd.securepay.local
    admin.enabled: "false"          # une fois le SSO validé
    dex.config: |
      connectors:
        - type: gitlab
          id: gitlab
          name: GitLab
          config:
            baseURL: https://gitlab.com
            clientID: <application id>
            clientSecret: $argocd-dex-gitlab:clientSecret
            groups:
              - <groupe>/dev
              - <groupe>/ops
              - <groupe>/sec
              - <groupe>/auditeurs
  • $argocd-dex-gitlab:clientSecret lit un Secret portant le label app.kubernetes.io/part-of: argocd, alimenté par External Secrets

ArgoCD : RBAC par groupe et par projet

configs:
  rbac:
    policy.default: ""              # aucun droit par défaut
    scopes: "[groups]"
    policy.csv: |
      p, role:dev, applications, get, */*, allow
      p, role:dev, applications, sync, staging/*, allow
      p, role:ops, applications, get, */*, allow
      p, role:ops, applications, sync, */*, allow
      p, role:sec, applications, get, */*, allow
      p, role:auditeur, applications, get, */*, allow
      g, <groupe>/dev, role:dev
      g, <groupe>/ops, role:ops
      g, <groupe>/sec, role:sec
      g, <groupe>/auditeurs, role:auditeur
  • Format : p, sujet, ressource, action, projet/objet, effet puis g, groupe, rôle
  • exec (terminal web) reste désactivé : personne n'ouvre de shell en production depuis ArgoCD

Grafana : connexion GitLab

# values kube-prometheus-stack
grafana:
  grafana.ini:
    server:
      root_url: http://grafana.securepay.local
    auth.gitlab:
      enabled: true
      client_id: <application id>
      client_secret: $__file{/etc/secrets/grafana-gitlab/client_secret}
      scopes: openid email profile
      allowed_groups: <groupe>/dev <groupe>/ops <groupe>/sec <groupe>/auditeurs
      role_attribute_path: contains(groups[*], '<groupe>/ops') && 'Editor' || 'Viewer'
    auth:
      disable_login_form: true
  • L'administrateur local de Grafana devient un compte d'urgence, son mot de passe rejoint OpenBao

OpenBao : groupes externes

bao auth enable oidc
bao write auth/oidc/config oidc_discovery_url="https://gitlab.com" \
  oidc_client_id="<application id>" oidc_client_secret="<secret>" \
  default_role="humains"
bao write auth/oidc/role/humains user_claim="sub" groups_claim="groups_direct" \
  allowed_redirect_uris="http://localhost:8250/oidc/callback" token_ttl=1h

# Le groupe GitLab <groupe>/ops reçoit la politique securepay-ops
ACC=$(bao auth list -format=json | jq -r '."oidc/".accessor')
bao write identity/group name="ops" type="external" policies="securepay-ops"
GID=$(bao read -field=id identity/group/name/ops)
bao write identity/group-alias name="<groupe>/ops" mount_accessor="$ACC" canonical_id="$GID"
  • Connexion : bao login -method=oidc role=humains
  • Sur l'instance de production, seul le groupe ops reçoit une politique

RBAC Kubernetes


Rappels

  • Role et ClusterRole : un ensemble de droits (verbes sur des ressources)
  • RoleBinding : accorde un rôle dans un namespace ; ClusterRoleBinding : dans tout le cluster
  • Sujets : User, Group ou ServiceAccount
  • Rôles intégrés : view (lecture sans les Secrets), edit (écriture, Secrets inclus), admin
  • Un RoleBinding peut référencer une ClusterRole : on réutilise view namespace par namespace
  • Les droits sont additifs : il n'existe pas de règle « deny »

Lier les profils aux namespaces

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-view
  namespace: securepay-staging
subjects:
  - kind: Group
    name: oidc:<groupe>/dev
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io
  • Le préfixe oidc: est celui que l'API Server ajoutera aux groupes OIDC
  • Aucun binding pour dev dans securepay-prod : l'absence de droit est la règle

Les droits qui permettent d'escalader

  • create sur pods : monter n'importe quel Secret du namespace et utiliser n'importe quel ServiceAccount
  • get/list sur secrets : lire tous les secrets, y compris des jetons de ServiceAccount
  • pods/exec : ouvrir un shell dans un conteneur en cours d'exécution
  • Verbes escalate, bind et impersonate : s'attribuer ou emprunter des droits supplémentaires
  • Jokers * sur les verbes ou les ressources
  • Ces droits sont à réserver aux contrôleurs qui en ont besoin, et à tracer

Les identités des workloads

  • Un ServiceAccount dédié par workload, jamais le ServiceAccount default
  • automountServiceAccountToken: false sauf si le workload appelle l'API Kubernetes
  • Les contrôleurs (ESO, remédiateur, ArgoCD) n'ont que les verbes et namespaces nécessaires
  • Revue régulière : kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa> -n <ns>
  • Des outils comme KubeHound cartographient les chemins d'attaque issus du RBAC

Accès kubectl humain par OIDC

  • L'API Server valide lui-même les ID tokens : arguments --oidc-* ou configuration d'authentification structurée (AuthenticationConfiguration) des versions récentes
  • Il ne lit que l'ID token : les groupes doivent y figurer, d'où l'intérêt de Dex
  • Côté poste, le plugin kubelogin ouvre le navigateur et renouvelle les jetons
  • Le préfixe des groupes (oidc:) évite toute collision avec des groupes internes comme system:masters
  • Une fois en place, plus aucun kubeconfig administrateur ne circule hors compte d'urgence

Audit & revues d'accès


La politique d'audit de l'API Kubernetes

Niveau Ce qui est journalisé
None rien
Metadata utilisateur, verbe, ressource, horodatage, réponse
Request métadonnées + corps de la requête
RequestResponse métadonnées + corps de la requête et de la réponse
  • Les Secrets sont journalisés au niveau Metadata : on trace l'accès sans copier la valeur
  • La première règle qui correspond s'applique : l'ordre des règles compte

Exemple de politique

apiVersion: audit.k8s.io/v1
kind: Policy
omitStages: ["RequestReceived"]
rules:
  - level: None
    nonResourceURLs: ["/healthz*", "/readyz*", "/livez*", "/version"]
  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets", "configmaps", "serviceaccounts/token",
                    "pods/exec", "pods/attach", "pods/portforward"]
  - level: RequestResponse
    verbs: ["create", "update", "patch", "delete"]
    resources:
      - group: "rbac.authorization.k8s.io"
  - level: Metadata
    verbs: ["create", "update", "patch", "delete"]
  - level: None
  • Un kubectl exec passe par WebSocket et apparaît avec le verbe get : d'où la règle explicite sur pods/exec
  • Les modifications RBAC sont tracées avec leur contenu, les lectures courantes ignorées

Activer l'audit sur k3s

k3d cluster create securepay --servers 1 --agents 2 \
  --volume "$PWD/audit-policy.yaml:/var/lib/rancher/k3s/server/audit-policy.yaml@server:0" \
  --k3s-arg "--kube-apiserver-arg=audit-policy-file=/var/lib/rancher/k3s/server/audit-policy.yaml@server:0" \
  --k3s-arg "--kube-apiserver-arg=audit-log-path=/var/lib/rancher/k3s/server/logs/audit.log@server:0" \
  --k3s-arg "--kube-apiserver-arg=audit-log-maxage=7@server:0"
  • Le journal est écrit sur le nœud server, au format JSON, un événement par ligne
  • Un collecteur (Grafana Alloy, Vector) déployé sur ce nœud l'envoie dans Loki
  • Les journaux d'audit d'OpenBao et d'ArgoCD rejoignent la même stack

Exploiter les journaux

# Shell ouvert dans un pod de production
{job="kube-audit"} | json | objectRef_subresource="exec" | objectRef_namespace="securepay-prod"

# Utilisation du compte d'urgence
{job="kube-audit"} | json | user_username="system:admin"

# Lecture de secrets par un humain
{job="kube-audit"} | json | objectRef_resource="secrets" | user_username!~"system:serviceaccount:.*"
  • Chaque requête alimente une règle d'alerte du ruler Loki ou un panneau de tableau de bord
  • Les mêmes questions doivent trouver une réponse en moins d'une minute pendant un incident

Tester les droits automatiquement

# L'API répond sans emprunter d'identité : SubjectAccessReview
kubectl create --validate=false -o jsonpath='{.status.allowed}' -f - <<EOF
apiVersion: authorization.k8s.io/v1
kind: SubjectAccessReview
spec:
  user: alice
  groups: ["oidc:<groupe>/dev"]
  resourceAttributes:
    namespace: securepay-prod
    verb: get
    resource: pods
EOF
# Attendu : false
  • Un script parcourt la matrice des profils et échoue au premier droit inattendu
  • Exécuté en CI avec un ServiceAccount qui n'a que le droit de créer des subjectaccessreviews
  • --validate=false évite que kubectl tente de lister les CRD, droit que ce ServiceAccount n'a pas

Anti-patterns à proscrire

  • Comptes partagés (admin, ops) dans ArgoCD, Grafana ou OpenBao
  • Profils humains avec edit ou cluster-admin « pour dépanner »
  • Droits attribués à des individus plutôt qu'à des groupes
  • Compte d'urgence utilisé au quotidien ou jamais renouvelé
  • Journaux d'audit au niveau RequestResponse sur les Secrets
  • Revue des accès jamais faite, départs non répercutés

À retenir

  • Les profils sont des groupes du fournisseur d'identité, et chaque outil s'y branche par OIDC
  • Écrire, approuver et déployer en production sont trois droits distincts
  • RBAC au moindre privilège : view par namespace, aucun droit d'écriture humain en production, attention aux verbes qui permettent d'escalader
  • Le compte d'urgence existe, mais il est conservé hors ligne, alerté et renouvelé
  • L'audit trace l'accès aux secrets sans en copier la valeur, et les droits se testent en CI

Pour aller plus loin