Aller au contenu
Sécurité medium

Dex : fédérateur d'identité pour Sigstore privé

29 min de lecture

Quand vous utilisez Sigstore en mode keyless (signature sans clé privée), vous devez prouver votre identité. En production publique, GitHub ou Google font ce travail. Mais dans un environnement air-gapped (déconnecté d'Internet), comment authentifier vos développeurs ?

C'est là qu'intervient Dex : un serveur d'identité qui traduit vos systèmes d'authentification existants (Active Directory, LDAP, Okta...) en un protocole standard que Sigstore comprend.

  • Comprendre le rôle de Dex comme fédérateur OIDC devant Fulcio.
  • Déployer Dex sur Kubernetes avec le chart Helm officiel.
  • Configurer un connecteur LDAP, SAML ou GitLab selon votre annuaire.
  • Raccorder Dex à Fulcio et signer une image en mode keyless.
  • Durcir l'installation : tokens courts, secrets externalisés, connecteur de test banni.

Le problème : l'authentification en environnement isolé

Section intitulée « Le problème : l'authentification en environnement isolé »

Imaginons votre situation :

  • Vous voulez utiliser Sigstore pour signer vos images de conteneurs
  • Votre infrastructure est déconnectée d'Internet (air-gapped)
  • Vous ne pouvez donc pas utiliser GitHub ou Google comme fournisseur d'identité
  • Mais vos développeurs ont déjà des comptes dans votre Active Directory

Comment faire le lien entre votre annuaire d'entreprise et Sigstore ?

Fulcio (la CA de Sigstore) parle un protocole précis : OpenID Connect (OIDC). Il attend :

  • Un endpoint de découverte (/.well-known/openid-configuration)
  • Des tokens JWT signés contenant l'identité de l'utilisateur
  • Des métadonnées standardisées (email, groupes, etc.)

Votre entreprise utilise probablement :

  • Active Directory ou LDAP pour les comptes utilisateurs
  • SAML avec Okta, ADFS ou Azure AD pour le SSO
  • GitLab ou GitHub Enterprise en interne

Ces systèmes ne parlent pas directement OIDC, ou pas de la façon attendue par Sigstore.

Dex est un fédérateur d'identité. Il se connecte à vos systèmes d'authentification et expose une interface OIDC standard.

Architecture Dex : vos systèmes d'identité (AD, SAML, GitLab) connectés à Dex
qui traduit tout en OIDC pour
Fulcio

Avant de déployer Dex, comprenez ces concepts clés.

OpenID Connect est un protocole d'authentification construit sur OAuth 2.0. Il permet à une application de vérifier l'identité d'un utilisateur et d'obtenir des informations basiques sur son profil.

Le flux simplifié :

  1. L'utilisateur veut signer un artefact avec Cosign
  2. Cosign redirige vers Dex : « Qui est cet utilisateur ? »
  3. Dex redirige vers Active Directory : « Connectez-vous »
  4. L'utilisateur entre son login/mot de passe AD
  5. AD confirme : « C'est bien jean.dupont@entreprise.fr »
  6. Dex génère un token JWT avec cette identité
  7. Cosign envoie ce token à Fulcio pour obtenir un certificat

Ces quatre notions reviennent dans chaque fichier de configuration et dans chaque message d'erreur. Celle qui cause le plus d'échecs est l'issuer : c'est une chaîne de caractères comparée octet par octet entre Dex, Fulcio et le client. Un slash final en trop, ou http au lieu de https, et le token est rejeté sans explication utile.

ComposantRôleExemple
IssuerURL publique de Dex, identifie le serveurhttps://dex.mon-entreprise.internal
ConnectorPont vers une source d'identitéLDAP, SAML, GitLab...
ClientApplication autorisée à utiliser DexCosign, Kubernetes, votre app
Token JWTPreuve d'identité signée et temporaireContient email, groupes, expiration

Quand Dex authentifie un utilisateur, il génère un token JWT (JSON Web Token). C'est un document signé contenant l'identité :

{
"iss": "https://dex.mon-entreprise.internal",
"sub": "Cg0wMDAwMDAwMDAwMDAw",
"aud": "sigstore",
"exp": 1735480800,
"iat": 1735480200,
"email": "jean.dupont@entreprise.fr",
"email_verified": true,
"groups": ["developers", "devops"],
"name": "Jean Dupont"
}

Les champs importants :

  • iss (issuer) : qui a émis ce token (Dex)
  • sub (subject) : identifiant unique de l'utilisateur
  • aud (audience) : pour qui ce token est destiné (Sigstore)
  • exp : date d'expiration (le token est temporaire)
  • email : l'identité qui sera dans le certificat Fulcio

Dex supporte de nombreuses sources d'identité. Choisissez selon votre contexte.

Un point compte autant que la difficulté d'installation : tous les connecteurs ne fournissent pas les mêmes claims. Le connecteur SAML, par exemple, n'émet pas de refresh token parce que le protocole ne prévoit aucun moyen non interactif de rafraîchir une assertion, ce qui interdit l'usage hors ligne. La colonne « Complexité » ci-dessous estime l'effort de mise en place, pas la richesse de l'intégration.

Vous avez...ConnecteurComplexité
Active DirectoryldapMoyenne
OpenLDAPldapMoyenne
Okta, Ping, OneLoginsamlÉlevée
Azure AD (Entra ID)microsoft ou samlMoyenne
GitLab internegitlabFaible
GitHub EnterprisegithubFaible
KeycloakoidcMoyenne
Rien (tests)mockCallbackFaible

Dex peut agréger plusieurs sources. L'utilisateur choisit lors de la connexion :

Écran de sélection du provider d'authentification dans Dex

C'est utile si vous avez :

  • Des développeurs internes (AD) et des prestataires (GitLab)
  • Un SSO d'entreprise (SAML) et un fallback local (LDAP)

Avant de commencer, vérifiez que vous avez :

Le déploiement se fait avec le chart Helm officiel. Les cinq étapes ci-dessous partent d'une configuration volontairement minimale, juste de quoi faire démarrer le pod ; les connecteurs et les clients viennent ensuite. Gardez un terminal ouvert sur les logs du pod (kubectl logs), c'est là que Dex signale une erreur de configuration au démarrage.

  1. Ajouter le repo Helm officiel

    Fenêtre de terminal
    helm repo add dex https://charts.dexidp.io
    helm repo update

    Vérifiez que le repo est bien ajouté :

    Fenêtre de terminal
    helm search repo dex
    # NAME CHART VERSION APP VERSION DESCRIPTION
    # dex/dex 0.24.1 2.44.0 OpenID Connect (OIDC) identity ...
  2. Créer le namespace dédié

    Fenêtre de terminal
    kubectl create namespace dex
  3. Préparer le fichier de configuration

    Créez un fichier dex-values.yaml. Nous allons le remplir progressivement dans les sections suivantes.

    dex-values.yaml
    # Configuration de base - on complète ensuite
    config:
    issuer: https://dex.votre-domaine.internal
    storage:
    type: kubernetes
    config:
    inCluster: true
  4. Déployer Dex

    Fenêtre de terminal
    helm upgrade --install dex dex/dex \
    --namespace dex \
    --values dex-values.yaml \
    --wait
  5. Vérifier le déploiement

    Fenêtre de terminal
    # Les pods doivent être Running
    kubectl get pods -n dex
    # NAME READY STATUS RESTARTS AGE
    # dex-7d8f9b6c5d-xxxxx 1/1 Running 0 30s
    # Vérifier les logs
    kubectl logs -n dex deploy/dex

Voici une configuration détaillée et commentée pour un environnement de production.

Ce fichier concentre tous les réglages : l'issuer, le stockage des sessions, les clients autorisés, les connecteurs et l'expiration des tokens. Quatre points méritent votre attention avant de copier ce modèle, ils sont repris en commentaire dans le YAML : l'issuer sans slash final, le secret client à régénérer, le stockage kubernetes qui persiste les sessions dans des CRD, et l'expiration courte des ID tokens imposée par Sigstore.

dex-values.yaml
# ══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION PRINCIPALE
# ══════════════════════════════════════════════════════════════════════════════
config:
# URL publique de Dex
# IMPORTANT : doit correspondre EXACTEMENT à votre Ingress
# Pas de slash final !
issuer: https://dex.sigstore.internal
# ────────────────────────────────────────────────────────────────────────────
# Stockage des tokens et sessions
# ────────────────────────────────────────────────────────────────────────────
storage:
# Pour un déploiement simple, utiliser le stockage Kubernetes (CRDs)
type: kubernetes
config:
inCluster: true
# ────────────────────────────────────────────────────────────────────────────
# Serveur web
# ────────────────────────────────────────────────────────────────────────────
web:
# Port HTTP interne (TLS géré par l'Ingress)
http: 0.0.0.0:5556
# ────────────────────────────────────────────────────────────────────────────
# Clients autorisés (applications qui utilisent Dex)
# ────────────────────────────────────────────────────────────────────────────
staticClients:
# Client pour Sigstore (Cosign/Fulcio)
- id: sigstore
name: "Sigstore"
# Secret partagé - CHANGEZ-LE !
# Générez avec : openssl rand -base64 32 | tr -d '/+=' | cut -c1-32
secret: "CHANGEZ-MOI-secret-32-caracteres"
# URIs de callback autorisées
redirectURIs:
# Callback local pour cosign CLI
- "http://localhost:8080/callback"
- "http://127.0.0.1:8080/callback"
# Pour le flow "device code" (CI/CD)
- "urn:ietf:wg:oauth:2.0:oob"
# ────────────────────────────────────────────────────────────────────────────
# Connecteurs d'identité (voir section suivante)
# ────────────────────────────────────────────────────────────────────────────
connectors: []
# On remplit cette section selon votre source d'identité
# ────────────────────────────────────────────────────────────────────────────
# Expiration des tokens
# ────────────────────────────────────────────────────────────────────────────
expiry:
# Durée de validité du token ID (court pour Sigstore)
idTokens: "10m"
# Durée avant expiration des refresh tokens inutilisés
refreshTokens:
validIfNotUsedFor: "168h" # 7 jours
# ────────────────────────────────────────────────────────────────────────────
# Options OAuth2
# ────────────────────────────────────────────────────────────────────────────
oauth2:
# Ne pas demander confirmation à chaque connexion
skipApprovalScreen: true
# ══════════════════════════════════════════════════════════════════════════════
# INGRESS (exposition HTTPS)
# ══════════════════════════════════════════════════════════════════════════════
ingress:
enabled: true
className: nginx # ou traefik, selon votre setup
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod" # ou votre issuer
hosts:
- host: dex.sigstore.internal
paths:
- path: /
pathType: Prefix
tls:
- secretName: dex-tls
hosts:
- dex.sigstore.internal
# ══════════════════════════════════════════════════════════════════════════════
# RESSOURCES ET RÉPLICAS
# ══════════════════════════════════════════════════════════════════════════════
replicaCount: 2 # HA
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi

Ne laissez pas le secret par défaut ! Générez-en un :

Fenêtre de terminal
# Générer un secret aléatoire de 32 caractères
SECRET=$(openssl rand -base64 32 | tr -d '/+=' | cut -c1-32)
echo "Votre secret : $SECRET"
# Copiez-le dans dex-values.yaml

Voici les configurations détaillées pour les sources d'identité les plus courantes.

Le connecteur le plus courant en entreprise.

dex-values.yaml (section connectors)
config:
connectors:
- type: ldap
id: activedirectory
name: "Active Directory"
config:
# ──────────────────────────────────────────────────────────────────────
# Connexion au serveur LDAP
# ──────────────────────────────────────────────────────────────────────
# Utilisez LDAPS (port 636) pour chiffrer la connexion
host: ldap.entreprise.local:636
# Certificat CA pour valider le serveur LDAP
# Option 1 : chemin vers le fichier (monter un secret)
rootCA: /etc/dex/ldap-ca/ca.crt
# Option 2 : contenu encodé en base64
# rootCAData: "LS0tLS1CRUdJTi..."
# Pour les tests uniquement (JAMAIS en production !)
# insecureSkipVerify: true
# ──────────────────────────────────────────────────────────────────────
# Compte de service pour les recherches LDAP
# ──────────────────────────────────────────────────────────────────────
# DN du compte de service
bindDN: "CN=svc-dex,OU=Services,DC=entreprise,DC=local"
# Mot de passe (utiliser une variable d'environnement)
bindPW: "${LDAP_BIND_PASSWORD}"
# ──────────────────────────────────────────────────────────────────────
# Recherche des utilisateurs
# ──────────────────────────────────────────────────────────────────────
userSearch:
# Où chercher les utilisateurs
baseDN: "OU=Utilisateurs,DC=entreprise,DC=local"
# Filtre LDAP (ici : comptes utilisateurs actifs)
filter: "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
# Attribut utilisé comme login
# Active Directory : sAMAccountName
# OpenLDAP : uid
username: sAMAccountName
# Attribut unique identifiant l'utilisateur
idAttr: DN
# Attribut email (sera dans le certificat Fulcio)
emailAttr: mail
# Attribut nom complet
nameAttr: displayName
# ──────────────────────────────────────────────────────────────────────
# Recherche des groupes (optionnel mais recommandé)
# ──────────────────────────────────────────────────────────────────────
groupSearch:
# Où chercher les groupes
baseDN: "OU=Groupes,DC=entreprise,DC=local"
# Filtre (groupes de sécurité)
filter: "(objectClass=group)"
# Comment lier utilisateurs et groupes
userMatchers:
- userAttr: DN
groupAttr: member
# Attribut nom du groupe
nameAttr: cn

Préparer le secret LDAP CA :

Fenêtre de terminal
# Créer un secret avec le certificat CA de votre AD
kubectl create secret generic ldap-ca \
--from-file=ca.crt=/chemin/vers/ca-ldap.crt \
-n dex

Ajouter le montage dans values :

dex-values.yaml (ajouter)
# Monter le certificat CA LDAP
volumes:
- name: ldap-ca
secret:
secretName: ldap-ca
volumeMounts:
- name: ldap-ca
mountPath: /etc/dex/ldap-ca
readOnly: true
# Variable d'environnement pour le mot de passe
env:
- name: LDAP_BIND_PASSWORD
valueFrom:
secretKeyRef:
name: dex-ldap-credentials
key: password

Créer le secret du mot de passe :

Fenêtre de terminal
kubectl create secret generic dex-ldap-credentials \
--from-literal=password='VotreMotDePasseAD' \
-n dex

Une fois Dex déployé et fonctionnel, configurez Fulcio pour l'utiliser.

Avant d'intégrer avec Fulcio, validez Dex seul :

Fenêtre de terminal
# 1. Vérifier l'endpoint de découverte OIDC
curl -s https://dex.sigstore.internal/.well-known/openid-configuration | jq .
# Vous devez voir quelque chose comme :
# {
# "issuer": "https://dex.sigstore.internal",
# "authorization_endpoint": "https://dex.sigstore.internal/auth",
# "token_endpoint": "https://dex.sigstore.internal/token",
# "jwks_uri": "https://dex.sigstore.internal/keys",
# ...
# }
# 2. Vérifier les clés de signature
curl -s https://dex.sigstore.internal/keys | jq .

Dans votre configuration Fulcio (ou scaffold values), ajoutez Dex comme issuer OIDC autorisé :

fulcio-values.yaml
fulcio:
server:
oidcIssuers:
# Votre instance Dex
- issuer: "https://dex.sigstore.internal"
clientID: "sigstore"
type: "email"
# Claim contenant l'identité (sera dans le certificat)
# "email" pour la plupart des cas
# "sub" si vous voulez l'ID unique
subjectClaim: "email"

C'est le test de bout en bout : si cette commande aboutit, toute la chaîne fonctionne. Les trois variables COSIGN_* désignent respectivement votre Dex, votre Fulcio et votre Rekor internes. À l'exécution, cosign ouvre un navigateur vers Dex, vous vous authentifiez auprès de votre annuaire, et Fulcio délivre un certificat éphémère portant votre email. Aucune clé privée n'est stockée, c'est tout l'intérêt du mode keyless.

Fenêtre de terminal
# Configurer Cosign pour utiliser votre Dex
export COSIGN_OIDC_ISSUER=https://dex.sigstore.internal
export COSIGN_FULCIO_URL=https://fulcio.sigstore.internal
export COSIGN_REKOR_URL=https://rekor.sigstore.internal
# Signer une image
# Dex va ouvrir le navigateur pour l'authentification
cosign sign --yes mon-registry.internal/mon-image:tag

Quatre erreurs reviennent systématiquement lors d'une première intégration. Elles se ressemblent dans le message mais pas dans la cause : invalid_client et redirect_uri_mismatch viennent d'une incohérence de configuration client, l'échec LDAP d'un problème réseau ou d'annuaire, et le rejet Fulcio presque toujours d'une issuer URL différente entre Dex et Fulcio. Ouvrez l'onglet qui correspond à votre message exact.

Erreur : invalid_client lors de l'authentification

Cause : Le client ID ou le secret ne correspond pas

Solution :

Fenêtre de terminal
# Vérifier la configuration des clients
kubectl get configmap dex -n dex -o yaml | grep -A 20 staticClients
# Le client ID dans Cosign doit correspondre
echo $COSIGN_OIDC_CLIENT_ID

Quand aucun des cas ci-dessus ne colle, passez le logger en debug : Dex trace alors chaque étape de l'échange OAuth, y compris le contenu des claims reçus de l'annuaire, ce qui révèle un attribut mal mappé ou un groupe absent. Le format json facilite l'exploitation dans une pile de logs centralisée. Repassez en info une fois le diagnostic terminé, le niveau debug est verbeux et peut exposer des informations d'identité dans les journaux.

dex-values.yaml
config:
logger:
level: debug # info, warn, error, debug
format: json

Puis consultez les logs :

Fenêtre de terminal
kubectl logs -n dex deploy/dex -f

Cette liste sépare ce qui doit être vrai avant d'exposer Dex à de vrais utilisateurs. La ligne la plus importante est « Connecteur » : un mockCallback ou un staticPasswords laissé actif accepte n'importe qui et réduit à néant toute la chaîne de confiance Sigstore. Vérifiez chaque ligne, aucune n'est optionnelle en production.

ÉlémentVérification
TLSDex accessible uniquement en HTTPS
SecretsStockés dans des Secrets Kubernetes, pas en clair
ConnecteurPas de mockCallback ni staticPasswords
Expiration tokens10 minutes max pour les ID tokens
IngressAccès restreint (IP whitelist si possible)
LogsCentralisés et surveillés
BackupSi PostgreSQL, backup de la base

Ces réglages traduisent la checklist en configuration concrète. Trois d'entre eux comptent plus que les autres : enablePasswordDB: false coupe toute authentification locale par mot de passe, runAsNonRoot: true empêche le conteneur de tourner en root, et la network policy restreint qui peut joindre Dex sur le réseau du cluster. Les durées d'expiration courtes limitent la fenêtre d'exploitation d'un token volé.

dex-values.yaml (production)
config:
# Tokens courts
expiry:
idTokens: "10m"
signingKeys: "6h"
deviceRequests: "5m"
# Pas d'authentification par mot de passe
enablePasswordDB: false
# Forcer HTTPS dans les redirections
oauth2:
skipApprovalScreen: true
# responseTypes limités
responseTypes: ["code"]
# Network policy
networkPolicy:
enabled: true
# Pod Security
securityContext:
runAsNonRoot: true
runAsUser: 1000

Ces cinq concepts sont ceux à mémoriser avant de passer à la mise en production. Si vous ne deviez en retenir qu'un, ce serait la cohérence de l'issuer : la majorité des échecs d'intégration se ramènent à une URL qui diffère d'un caractère entre Dex, Fulcio et le client.

ConceptRésumé
Rôle de DexFédère vos systèmes d'identité vers OIDC
ConnecteurPont vers AD, LDAP, SAML, GitLab...
ClientApplication autorisée (Sigstore = sigstore)
Token JWTPreuve d'identité temporaire (10 min)
IssuerURL unique, doit correspondre partout

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn