diff --git a/docs/PKI-INTERNE.md b/docs/PKI-INTERNE.md new file mode 100644 index 0000000..b77b18d --- /dev/null +++ b/docs/PKI-INTERNE.md @@ -0,0 +1,279 @@ +# PKI Interne pour les Sites Internes + +## Vue d'ensemble + +Cette architecture implémente une **PKI (Public Key Infrastructure) privée** pour gérer les certificats TLS des sites internes (domaines `.local`), tout en conservant Let's Encrypt pour les sites publics (domaines `.fr`). + +## Architecture + +### Stratégie hybride + +- **Sites publics** (`.fr`) → **Let's Encrypt** (`letsencrypt-prod`) + - Nécessite un accès Internet + - Validation via HTTP-01 challenge + - Certificats reconnus par tous les navigateurs + +- **Sites internes** (`.local`) → **PKI Interne** (`ca-issuer`) + - Pas besoin d'accès Internet + - Contrôle total sur les certificats + - Certificats signés par votre propre CA root + +## Composants + +### 1. CA Root (`ca-root-issuer`) + +Génère la **Certificate Authority root** de votre PKI interne. + +```yaml +ClusterIssuer: ca-root-issuer +Certificate: ca-root-certificate +Secret: ca-root-secret +``` + +**Durée de vie** : 10 ans (configurable) + +### 2. CA Issuer (`ca-issuer`) + +Utilise la CA root pour signer les certificats des applications internes. + +```yaml +ClusterIssuer: ca-issuer +``` + +### 3. Distribution du certificat CA root + +Le certificat CA root doit être distribué aux clients (navigateurs, applications) pour qu'ils fassent confiance aux certificats signés. + +**ConfigMap** : `ca-root-certificate` dans le namespace `certificates-ops` + +## Avantages de la PKI Interne + +✅ **Pas d'accès Internet requis** - Les clusters DEV/RCT/PRD peuvent générer des certificats sans Internet +✅ **Contrôle total** - Vous gérez la durée de vie, les politiques, etc. +✅ **Pas de limites de taux** - Pas de restrictions comme Let's Encrypt (50 certificats/semaine) +✅ **Domaines internes** - Support natif pour `.local`, `.internal`, etc. +✅ **Sécurité renforcée** - Certificats uniquement pour votre infrastructure interne +✅ **Renouvellement automatique** - cert-manager gère le renouvellement automatiquement + +## Utilisation + +### Pour un site interne (`.local`) + +Créez un Certificate qui utilise `ca-issuer` : + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: harbor-tls + namespace: certificates-ops +spec: + secretName: harbor-tls + issuerRef: + name: ca-issuer + kind: ClusterIssuer + dnsNames: + - harbor.gkdomaine.local + - harbor.dev.gkdomaine.local +``` + +### Pour un site public (`.fr`) + +Utilisez `letsencrypt-prod` comme avant : + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: homarr-dev-tls + namespace: certificates-ops +spec: + secretName: homarr-dev-tls + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + dnsNames: + - homarr.dev.gkdomaine.fr +``` + +## Installation + +### Étape 1 : Déployer la CA root + +Les ressources sont déployées automatiquement via ArgoCD : + +```bash +# Vérifier que la CA root est créée +kubectl get certificate ca-root-certificate -n certificates-ops --context=cluster-ops + +# Vérifier le ClusterIssuer CA +kubectl get clusterissuer ca-issuer --context=cluster-ops +``` + +### Étape 2 : Extraire le certificat CA root + +```bash +# Extraire le certificat CA root +kubectl get secret ca-root-secret -n certificates-ops \ + --context=cluster-ops \ + -o jsonpath='{.data.tls\.crt}' | base64 -d > ca-root.crt + +# Vérifier le certificat +openssl x509 -in ca-root.crt -text -noout +``` + +### Étape 3 : Distribuer le certificat CA root + +#### Option A : Ajouter au ConfigMap (pour distribution dans Kubernetes) + +```bash +# Mettre à jour le ConfigMap avec le certificat CA +kubectl create configmap ca-root-certificate \ + --from-file=ca.crt=ca-root.crt \ + -n certificates-ops \ + --context=cluster-ops \ + --dry-run=client -o yaml | kubectl apply -f - +``` + +#### Option B : Installer sur les machines clientes + +**Windows** : +```powershell +# Importer dans le magasin de certificats Windows +Import-Certificate -FilePath ca-root.crt -CertStoreLocation Cert:\LocalMachine\Root +``` + +**Linux** : +```bash +# Copier dans le magasin de certificats système +sudo cp ca-root.crt /usr/local/share/ca-certificates/gkdomaine-ca-root.crt +sudo update-ca-certificates +``` + +**macOS** : +```bash +# Importer dans le trousseau système +sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca-root.crt +``` + +#### Option C : Configurer dans les navigateurs + +- **Chrome/Edge** : Paramètres → Confidentialité et sécurité → Gérer les certificats → Autorités de certification → Importer +- **Firefox** : Paramètres → Confidentialité et sécurité → Certificats → Afficher les certificats → Autorités → Importer + +### Étape 4 : Synchroniser le ConfigMap vers les autres clusters + +Le ConfigMap `ca-root-certificate` peut être synchronisé vers les clusters DEV/RCT/PRD pour que les pods puissent faire confiance aux certificats : + +```bash +# Synchroniser vers cluster-dev +kubectl get configmap ca-root-certificate -n certificates-ops \ + --context=cluster-ops -o yaml | \ + kubectl apply --context=cluster-dev -f - +``` + +## Vérification + +### Vérifier la CA root + +```bash +# Vérifier le certificat CA root +kubectl get certificate ca-root-certificate -n certificates-ops --context=cluster-ops + +# Vérifier le secret CA root +kubectl get secret ca-root-secret -n certificates-ops --context=cluster-ops + +# Voir les détails du certificat +kubectl get secret ca-root-secret -n certificates-ops \ + --context=cluster-ops \ + -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout +``` + +### Vérifier un certificat signé par la CA + +```bash +# Vérifier un certificat d'application +kubectl get certificate harbor-tls -n certificates-ops --context=cluster-ops + +# Vérifier la chaîne de certificats +kubectl get secret harbor-tls -n certificates-ops \ + --context=cluster-ops \ + -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout + +# Vérifier que le certificat est signé par la CA root +kubectl get secret harbor-tls -n certificates-ops \ + --context=cluster-ops \ + -o jsonpath='{.data.tls\.crt}' | base64 -d | \ + openssl verify -CAfile <(kubectl get secret ca-root-secret -n certificates-ops \ + --context=cluster-ops -o jsonpath='{.data.tls\.crt}' | base64 -d) +``` + +## Migration des certificats existants + +Pour migrer un certificat de Let's Encrypt vers la PKI interne : + +1. **Modifier le Certificate** pour utiliser `ca-issuer` au lieu de `letsencrypt-prod` +2. **Supprimer l'ancien secret** pour forcer la régénération +3. **Vérifier** que le nouveau certificat est généré et signé par la CA root + +Exemple pour Harbor : + +```bash +# 1. Modifier le Certificate (dans Git) +# issuerRef.name: ca-issuer + +# 2. Supprimer l'ancien secret +kubectl delete secret harbor-tls -n certificates-ops --context=cluster-ops + +# 3. Vérifier la régénération +kubectl get certificate harbor-tls -n certificates-ops --context=cluster-ops -w +``` + +## Sécurité + +### Bonnes pratiques + +- 🔒 **Protéger la clé privée CA root** - Stockée dans `ca-root-secret`, ne jamais exposer +- 🔄 **Rotation périodique** - Planifier la rotation de la CA root avant expiration (tous les 10 ans) +- 📋 **Audit régulier** - Vérifier régulièrement les certificats émis +- 🚫 **Isolation** - Ne jamais utiliser la CA interne pour des sites publics +- 🔐 **Backup** - Sauvegarder régulièrement le secret `ca-root-secret` + +### Rotation de la CA root + +Quand la CA root approche de son expiration : + +1. Créer une nouvelle CA root (`ca-root-certificate-v2`) +2. Créer un nouveau ClusterIssuer (`ca-issuer-v2`) +3. Migrer progressivement les certificats vers la nouvelle CA +4. Mettre à jour les clients avec le nouveau certificat CA root + +## Troubleshooting + +### Le certificat n'est pas généré + +```bash +# Vérifier les logs de cert-manager +kubectl logs -n cert-manager-ops -l app.kubernetes.io/name=cert-manager --context=cluster-ops --tail=100 + +# Vérifier le Certificate +kubectl describe certificate -n certificates-ops --context=cluster-ops +``` + +### Le navigateur affiche "Certificat non fiable" + +- Vérifier que le certificat CA root est installé sur la machine cliente +- Vérifier que le certificat est bien signé par la CA root +- Vérifier la date de validité du certificat + +### Erreur "certificate signed by unknown authority" + +- Le certificat CA root n'est pas installé sur le client +- Vérifier que le ConfigMap `ca-root-certificate` est synchronisé vers le cluster cible + +## Références + +- [cert-manager CA Issuer Documentation](https://cert-manager.io/docs/configuration/ca/) +- [PKI Best Practices](https://www.ssl.com/article/pki-best-practices/) +- [Kubernetes Certificate Management](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/) + diff --git a/helm/certificates/ops/docs/README.md b/helm/certificates/ops/docs/README.md index 0ce67ad..1c0b573 100644 --- a/helm/certificates/ops/docs/README.md +++ b/helm/certificates/ops/docs/README.md @@ -1,6 +1,17 @@ -# Certificats Let's Encrypt pour le cluster OPS +# Gestion des Certificats TLS pour le cluster OPS -Ce chart gère les certificats Let's Encrypt générés par cert-manager dans le cluster OPS. +Ce chart gère les certificats TLS générés par cert-manager dans le cluster OPS. + +## Stratégie de certificats + +- **Sites publics** (`.fr`) → **Let's Encrypt** (`letsencrypt-prod`) + - Validation HTTP-01, nécessite accès Internet + - Certificats reconnus par tous les navigateurs + +- **Sites internes** (`.local`) → **PKI Interne** (`ca-issuer`) + - Certificats signés par votre CA root interne + - Pas besoin d'accès Internet + - Voir [PKI Interne](../../../../docs/PKI-INTERNE.md) pour plus de détails ## Structure @@ -28,7 +39,9 @@ metadata: spec: secretName: --tls issuerRef: - name: letsencrypt-prod # ou letsencrypt-staging pour les tests + name: letsencrypt-prod # Pour sites publics (.fr) + # OU + # name: ca-issuer # Pour sites internes (.local) kind: ClusterIssuer dnsNames: - diff --git a/helm/certificates/ops/templates/certificate-ca-root.yaml b/helm/certificates/ops/templates/certificate-ca-root.yaml new file mode 100644 index 0000000..53f2d2b --- /dev/null +++ b/helm/certificates/ops/templates/certificate-ca-root.yaml @@ -0,0 +1,26 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: ca-root-certificate + namespace: certificates-ops +spec: + # Ce certificat génère la CA root de votre PKI interne + secretName: ca-root-secret + issuerRef: + name: ca-root-issuer + kind: ClusterIssuer + commonName: "GK Domaine Internal CA Root" + dnsNames: + - "gkdomaine.local" + - "*.gkdomaine.local" + - "*.dev.gkdomaine.local" + - "*.rct.gkdomaine.local" + - "*.prd.gkdomaine.local" + isCA: true + duration: 87600h # 10 ans + usages: + - signing + - key encipherment + - cert sign + - crl sign + diff --git a/helm/certificates/ops/templates/cluster-issuer-ca-root.yaml b/helm/certificates/ops/templates/cluster-issuer-ca-root.yaml new file mode 100644 index 0000000..624b7f0 --- /dev/null +++ b/helm/certificates/ops/templates/cluster-issuer-ca-root.yaml @@ -0,0 +1,7 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: ca-root-issuer +spec: + selfSigned: {} + diff --git a/helm/certificates/ops/templates/cluster-issuer-ca.yaml b/helm/certificates/ops/templates/cluster-issuer-ca.yaml new file mode 100644 index 0000000..e46291a --- /dev/null +++ b/helm/certificates/ops/templates/cluster-issuer-ca.yaml @@ -0,0 +1,10 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: ca-issuer +spec: + ca: + secretName: ca-root-secret + # Le secret ca-root-secret contient la clé privée et le certificat de la CA root + # Il est généré par le Certificate ca-root-certificate ci-dessus + diff --git a/helm/certificates/ops/templates/configmap-ca-cert.yaml b/helm/certificates/ops/templates/configmap-ca-cert.yaml new file mode 100644 index 0000000..bb7df36 --- /dev/null +++ b/helm/certificates/ops/templates/configmap-ca-cert.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ca-root-certificate + namespace: certificates-ops + labels: + app: pki-internal + component: ca-root-cert +data: + # Le certificat CA root sera injecté ici par un script ou manuellement + # Ce ConfigMap peut être synchronisé vers les autres clusters pour que les clients + # puissent faire confiance aux certificats signés par cette CA + ca.crt: | + # Le certificat CA sera ajouté ici après la génération + # Pour l'extraire : kubectl get secret ca-root-secret -n certificates-ops -o jsonpath='{.data.tls\.crt}' | base64 -d + diff --git a/helm/certificates/ops/templates/harbor/certificate-dev.yaml b/helm/certificates/ops/templates/harbor/certificate-dev.yaml new file mode 100644 index 0000000..890a222 --- /dev/null +++ b/helm/certificates/ops/templates/harbor/certificate-dev.yaml @@ -0,0 +1,13 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: harbor-ops-tls + namespace: certificates-ops +spec: + secretName: harbor-ops-tls + issuerRef: + name: ca-issuer # PKI interne pour domaine .local + kind: ClusterIssuer + dnsNames: + - harbor.gkdomaine.local + diff --git a/helm/certificates/ops/templates/headlamp/certificate-dev.yaml b/helm/certificates/ops/templates/headlamp/certificate-dev.yaml index 20ba8ec..ea98eb5 100644 --- a/helm/certificates/ops/templates/headlamp/certificate-dev.yaml +++ b/helm/certificates/ops/templates/headlamp/certificate-dev.yaml @@ -6,7 +6,7 @@ metadata: spec: secretName: headlamp-dev-tls issuerRef: - name: letsencrypt-prod + name: ca-issuer kind: ClusterIssuer dnsNames: - - headlamp.dev.gkdomaine.fr + - headlamp.dev.gkdomaine.local diff --git a/helm/headlamp/dev/values.yaml b/helm/headlamp/dev/values.yaml index eb3639a..a952a45 100644 --- a/helm/headlamp/dev/values.yaml +++ b/helm/headlamp/dev/values.yaml @@ -20,7 +20,7 @@ headlamp: enabled: true className: traefik hosts: - - host: headlamp.dev.gkdomaine.fr + - host: headlamp.dev.gkdomaine.local paths: - path: / type: Prefix @@ -29,7 +29,7 @@ headlamp: tls: - secretName: headlamp-dev-tls hosts: - - headlamp.dev.gkdomaine.fr + - headlamp.dev.gkdomaine.local annotations: {} serviceAccount: diff --git a/scripts/extract-ca-root-cert.sh b/scripts/extract-ca-root-cert.sh new file mode 100644 index 0000000..2e02d1d --- /dev/null +++ b/scripts/extract-ca-root-cert.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# Script pour extraire le certificat CA root et le mettre à jour dans le ConfigMap +# Usage: ./extract-ca-root-cert.sh [--context CLUSTER_CONTEXT] [--output FILE] + +set -e + +CONTEXT="${KUBECTL_CONTEXT:-cluster-ops}" +NAMESPACE="certificates-ops" +SECRET_NAME="ca-root-secret" +CONFIGMAP_NAME="ca-root-certificate" +OUTPUT_FILE="" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --context) + CONTEXT="$2" + shift 2 + ;; + --output) + OUTPUT_FILE="$2" + shift 2 + ;; + *) + echo "Usage: $0 [--context CLUSTER_CONTEXT] [--output FILE]" + echo " --context: Contexte kubectl (défaut: cluster-ops)" + echo " --output: Fichier de sortie pour le certificat (optionnel)" + exit 1 + ;; + esac +done + +echo "=== Extraction du certificat CA root ===" +echo "Cluster: $CONTEXT" +echo "Namespace: $NAMESPACE" +echo "" + +# Vérifier que kubectl est disponible +if ! command -v kubectl &> /dev/null; then + echo "❌ kubectl n'est pas installé" + exit 1 +fi + +# Vérifier l'accès au cluster +if ! kubectl --context="$CONTEXT" cluster-info &> /dev/null; then + echo "❌ Impossible d'accéder au cluster $CONTEXT" + exit 1 +fi + +# Vérifier que le secret existe +if ! kubectl --context="$CONTEXT" get secret "$SECRET_NAME" -n "$NAMESPACE" &> /dev/null; then + echo "❌ Le secret $SECRET_NAME n'existe pas dans le namespace $NAMESPACE" + echo " Assurez-vous que la CA root a été générée :" + echo " kubectl get certificate ca-root-certificate -n $NAMESPACE --context=$CONTEXT" + exit 1 +fi + +# Extraire le certificat +echo "📋 Extraction du certificat depuis le secret..." +CA_CERT=$(kubectl --context="$CONTEXT" get secret "$SECRET_NAME" -n "$NAMESPACE" \ + -o jsonpath='{.data.tls\.crt}' | base64 -d) + +if [ -z "$CA_CERT" ]; then + echo "❌ Impossible d'extraire le certificat du secret" + exit 1 +fi + +# Sauvegarder dans un fichier si demandé +if [ -n "$OUTPUT_FILE" ]; then + echo "$CA_CERT" > "$OUTPUT_FILE" + echo "✅ Certificat sauvegardé dans: $OUTPUT_FILE" +else + TEMP_FILE=$(mktemp) + echo "$CA_CERT" > "$TEMP_FILE" + OUTPUT_FILE="$TEMP_FILE" + echo "✅ Certificat extrait dans un fichier temporaire" +fi + +# Afficher les informations du certificat +echo "" +echo "📜 Informations du certificat CA root:" +echo "$CA_CERT" | openssl x509 -text -noout | grep -E "Subject:|Issuer:|Not Before|Not After|X509v3 Subject Alternative Name" || true + +# Mettre à jour le ConfigMap +echo "" +echo "🔄 Mise à jour du ConfigMap $CONFIGMAP_NAME..." + +# Créer ou mettre à jour le ConfigMap +kubectl --context="$CONTEXT" create configmap "$CONFIGMAP_NAME" \ + --from-file=ca.crt="$OUTPUT_FILE" \ + -n "$NAMESPACE" \ + --dry-run=client -o yaml | \ + kubectl --context="$CONTEXT" apply -f - + +if [ $? -eq 0 ]; then + echo "✅ ConfigMap mis à jour avec succès" +else + echo "❌ Erreur lors de la mise à jour du ConfigMap" + exit 1 +fi + +# Nettoyer le fichier temporaire si créé +if [ -n "$TEMP_FILE" ] && [ -f "$TEMP_FILE" ]; then + rm "$TEMP_FILE" +fi + +echo "" +echo "=== Résumé ===" +echo "✅ Certificat CA root extrait" +echo "✅ ConfigMap $CONFIGMAP_NAME mis à jour dans le namespace $NAMESPACE" +echo "" +echo "📋 Prochaines étapes:" +echo "1. Distribuer le certificat CA root aux clients (navigateurs, machines)" +echo "2. Voir docs/PKI-INTERNE.md pour les instructions détaillées" +echo "" +echo "Pour extraire le certificat dans un fichier:" +echo " $0 --output ca-root.crt" +