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.
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é :
- L’utilisateur veut signer un artefact avec Cosign
- Cosign redirige vers Dex : « Qui est cet utilisateur ? »
- Dex redirige vers Active Directory : « Connectez-vous »
- L’utilisateur entre son login/mot de passe AD
- AD confirme : « C’est bien jean.dupont@entreprise.fr »
- Dex génère un token JWT avec cette identité
- Cosign envoie ce token à Fulcio pour obtenir un certificat
Les composants de Dex
| Composant | Rôle | Exemple |
|---|---|---|
| Issuer | URL publique de Dex, identifie le serveur | https://dex.mon-entreprise.internal |
| Connector | Pont vers une source d’identité | LDAP, SAML, GitLab… |
| Client | Application autorisée à utiliser Dex | Cosign, Kubernetes, votre app |
| Token JWT | Preuve d’identité signée et temporaire | Contient 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’utilisateuraud(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… | Connecteur | Complexité |
|---|---|---|
| Active Directory | ldap | ⭐⭐ |
| OpenLDAP | ldap | ⭐⭐ |
| Okta, Ping, OneLogin | saml | ⭐⭐⭐ |
| Azure AD (Entra ID) | microsoft ou saml | ⭐⭐ |
| GitLab interne | gitlab | ⭐ |
| GitHub Enterprise | github | ⭐ |
| Keycloak | oidc | ⭐⭐ |
| Rien (tests) | mockCallback | ⭐ |
Combiner plusieurs connecteurs
Dex peut agréger plusieurs sources. L’utilisateur choisit lors de la connexion :
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
-
Ajouter le repo Helm officiel
Terminal window helm repo add dex https://charts.dexidp.iohelm repo updateVé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 ... -
Créer le namespace dédié
Terminal window kubectl create namespace dex -
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 ensuiteconfig:issuer: https://dex.votre-domaine.internalstorage:type: kubernetesconfig:inCluster: true -
Déployer Dex
Terminal window helm upgrade --install dex dex/dex \--namespace dex \--values dex-values.yaml \--wait -
Vérifier le déploiement
Terminal window # Les pods doivent être Runningkubectl get pods -n dex# NAME READY STATUS RESTARTS AGE# dex-7d8f9b6c5d-xxxxx 1/1 Running 0 30s# Vérifier les logskubectl 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
# ══════════════════════════════════════════════════════════════════════════════# 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: 256MiGénérer un secret client sécurisé
Ne laissez pas le secret par défaut ! Générez-en un :
# Générer un secret aléatoire de 32 caractèresSECRET=$(openssl rand -base64 32 | tr -d '/+=' | cut -c1-32)echo "Votre secret : $SECRET"# Copiez-le dans dex-values.yamlConfigurer les connecteurs
Voici les configurations détaillées pour les sources d’identité les plus courantes.
Le connecteur le plus courant en entreprise.
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: cnPréparer le secret LDAP CA :
# Créer un secret avec le certificat CA de votre ADkubectl create secret generic ldap-ca \ --from-file=ca.crt=/chemin/vers/ca-ldap.crt \ -n dexAjouter le montage dans values :
# Monter le certificat CA LDAPvolumes: - 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 passeenv: - name: LDAP_BIND_PASSWORD valueFrom: secretKeyRef: name: dex-ldap-credentials key: passwordCréer le secret du mot de passe :
kubectl create secret generic dex-ldap-credentials \ --from-literal=password='VotreMotDePasseAD' \ -n dexPour les SSO d’entreprise utilisant SAML.
config: connectors: - type: saml id: okta name: "Okta SSO" config: # ────────────────────────────────────────────────────────────────────── # Configuration SAML # ──────────────────────────────────────────────────────────────────────
# URL de SSO de votre IdP # Trouvez-la dans la configuration de l'application SAML Okta ssoURL: https://votre-org.okta.com/app/xxxxx/sso/saml
# Certificat pour vérifier les assertions SAML # Téléchargez-le depuis Okta > Applications > votre app > Sign On ca: /etc/dex/saml-ca/okta.crt
# URI de callback (à configurer aussi dans Okta) redirectURI: https://dex.sigstore.internal/callback
# EntityID de Dex (identifiant unique côté Okta) entityIssuer: https://dex.sigstore.internal
# ────────────────────────────────────────────────────────────────────── # Mapping des attributs SAML → Claims OIDC # ──────────────────────────────────────────────────────────────────────
# Attribut SAML contenant l'email usernameAttr: email emailAttr: email
# Attribut SAML contenant les groupes (si configuré dans Okta) groupsAttr: groups
# Filtrer les groupes (optionnel) # allowedGroups: # - devops # - developersConfiguration côté Okta :
- Créer une nouvelle application SAML 2.0
- Single Sign-On URL :
https://dex.sigstore.internal/callback - Audience URI :
https://dex.sigstore.internal - Télécharger le certificat X.509
Pour un GitLab self-hosted.
config: connectors: - type: gitlab id: gitlab name: "GitLab Interne" config: # URL de votre GitLab baseURL: https://gitlab.entreprise.local
# Credentials OAuth2 (créés dans GitLab Admin > Applications) clientID: "${GITLAB_CLIENT_ID}" clientSecret: "${GITLAB_CLIENT_SECRET}"
# URI de callback (doit être dans la config GitLab) redirectURI: https://dex.sigstore.internal/callback
# Optionnel : restreindre à certains groupes GitLab groups: - devops - platform-team # Si vide, tous les utilisateurs GitLab peuvent se connecterCréer l’application OAuth2 dans GitLab :
- Admin Area → Applications → New Application
- Name :
Dex Sigstore - Redirect URI :
https://dex.sigstore.internal/callback - Scopes :
read_user,openid,email - Notez le Client ID et Secret
Pour tester sans source d’identité réelle.
config: connectors: # Connecteur de test - accepte n'importe quel login - type: mockCallback id: mock name: "Test Login"
# Optionnel : utilisateurs avec mot de passe en dur # UNIQUEMENT pour les tests ! enablePasswordDB: true staticPasswords: - email: testuser@example.com # Hash bcrypt du mot de passe "password" # Générer avec : htpasswd -nbBC 10 "" "votre-mot-de-passe" | tr -d ':\n' hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" username: testuser userID: "test-user-001"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 :
# 1. Vérifier l'endpoint de découverte OIDCcurl -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 signaturecurl -s https://dex.sigstore.internal/keys | jq .Configurer Fulcio
Dans votre configuration Fulcio (ou scaffold values), ajoutez Dex comme issuer OIDC autorisé :
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
# Configurer Cosign pour utiliser votre Dexexport COSIGN_OIDC_ISSUER=https://dex.sigstore.internalexport COSIGN_FULCIO_URL=https://fulcio.sigstore.internalexport COSIGN_REKOR_URL=https://rekor.sigstore.internal
# Signer une image# Dex va ouvrir le navigateur pour l'authentificationcosign sign --yes mon-registry.internal/mon-image:tagDépannage
Problèmes courants et solutions
Erreur : invalid_client lors de l’authentification
Cause : Le client ID ou le secret ne correspond pas
Solution :
# Vérifier la configuration des clientskubectl get configmap dex -n dex -o yaml | grep -A 20 staticClients
# Le client ID dans Cosign doit correspondreecho $COSIGN_OIDC_CLIENT_IDErreur : redirect_uri_mismatch
Cause : L’URI de callback n’est pas dans la liste autorisée
Solution : Ajouter toutes les variantes dans redirectURIs :
staticClients: - id: sigstore redirectURIs: - "http://localhost:8080/callback" - "http://127.0.0.1:8080/callback" # Ajouter aussi 127.0.0.1 - "http://[::1]:8080/callback" # IPv6 localhost - "urn:ietf:wg:oauth:2.0:oob" # Device code flowErreur : Connexion LDAP échoue
Diagnostic :
# Tester depuis le pod Dexkubectl exec -it deploy/dex -n dex -- sh
# Test de connexion LDAPldapsearch -x -H ldaps://ldap.entreprise.local:636 \ -D "CN=svc-dex,OU=Services,DC=entreprise,DC=local" \ -w "motdepasse" \ -b "OU=Utilisateurs,DC=entreprise,DC=local" \ "(sAMAccountName=testuser)"Causes fréquentes :
- Certificat CA manquant ou incorrect
- Mauvais DN de bind
- Firewall bloquant le port 636
- Compte de service désactivé
Erreur : Fulcio rejette le token
Diagnostic :
# Vérifier les logs Fulciokubectl logs -n sigstore deploy/fulcio-server | grep -i "oidc\|issuer\|token"Causes fréquentes :
- Issuer URL différente entre Dex et Fulcio
- Client ID non autorisé dans Fulcio
- Horloge désynchronisée entre serveurs (NTP)
Activer les logs détaillés
config: logger: level: debug # info, warn, error, debug format: jsonPuis consultez les logs :
kubectl logs -n dex deploy/dex -fSécurité et bonnes pratiques
Checklist de production
| Élément | Vérification |
|---|---|
| TLS | Dex accessible uniquement en HTTPS |
| Secrets | Stockés dans des Secrets Kubernetes, pas en clair |
| Connecteur | Pas de mockCallback ni staticPasswords |
| Expiration tokens | 10 minutes max pour les ID tokens |
| Ingress | Accès restreint (IP whitelist si possible) |
| Logs | Centralisés et surveillés |
| Backup | Si PostgreSQL, backup de la base |
Hardening recommandé
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 policynetworkPolicy: enabled: true
# Pod SecuritysecurityContext: runAsNonRoot: true runAsUser: 1000À retenir
| Concept | Résumé |
|---|---|
| Rôle de Dex | Fédère vos systèmes d’identité vers OIDC |
| Connecteur | Pont vers AD, LDAP, SAML, GitLab… |
| Client | Application autorisée (Sigstore = sigstore) |
| Token JWT | Preuve d’identité temporaire (10 min) |
| Issuer | URL unique, doit correspondre partout |