Aller au contenu

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

Mise à jour :

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.

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 ?

Ce que Sigstore attend

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.)

Ce que vous avez

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.

La solution : Dex comme traducteur

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

Concepts fondamentaux

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

Qu’est-ce qu’OIDC ?

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

Les composants de Dex

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

Le token JWT : au cœur du système

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

Choisir vos connecteurs

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

Quel connecteur pour votre situation ?

Vous avez…ConnecteurComplexité
Active Directoryldap⭐⭐
OpenLDAPldap⭐⭐
Okta, Ping, OneLoginsaml⭐⭐⭐
Azure AD (Entra ID)microsoft ou saml⭐⭐
GitLab internegitlab
GitHub Enterprisegithub
Keycloakoidc⭐⭐
Rien (tests)mockCallback

Combiner plusieurs connecteurs

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)

Installation de Dex

Prérequis

Avant de commencer, vérifiez que vous avez :

  • Un cluster Kubernetes (v1.25 ou plus récent)
  • Helm 3.x installé
  • cert-manager déployé (pour les certificats TLS)
  • Un Ingress Controller (nginx, Traefik…)
  • Les informations de connexion à votre annuaire (LDAP, etc.)

Déploiement pas à pas

  1. Ajouter le repo Helm officiel

    Terminal window
    helm repo add dex https://charts.dexidp.io
    helm repo update

    Vérifiez que le repo est bien ajouté :

    Terminal window
    helm search repo dex
    # NAME CHART VERSION APP VERSION DESCRIPTION
    # dex/dex 0.18.0 2.39.0 ...
  2. Créer le namespace dédié

    Terminal window
    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

    Terminal window
    helm upgrade --install dex dex/dex \
    --namespace dex \
    --values dex-values.yaml \
    --wait
  5. Vérifier le déploiement

    Terminal window
    # 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

Configuration complète

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

Structure du fichier dex-values.yaml

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

Générer un secret client sécurisé

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

Terminal window
# 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

Configurer les connecteurs

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 :

Terminal window
# 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 :

Terminal window
kubectl create secret generic dex-ldap-credentials \
--from-literal=password='VotreMotDePasseAD' \
-n dex

Intégration avec Fulcio

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

Vérifier que Dex fonctionne

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

Terminal window
# 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 .

Configurer Fulcio

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"

Tester la signature keyless

Terminal window
# 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

Dépannage

Problèmes courants et solutions

Erreur : invalid_client lors de l’authentification

Cause : Le client ID ou le secret ne correspond pas

Solution :

Terminal window
# 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

Activer les logs détaillés

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

Puis consultez les logs :

Terminal window
kubectl logs -n dex deploy/dex -f

Sécurité et bonnes pratiques

Checklist de 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

Hardening recommandé

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

À retenir

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

Pour aller plus loin

Ressources externes