Files
argocd/scripts/sync-all-certificates.sh
2026-01-22 03:24:17 +01:00

435 lines
17 KiB
Bash
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Script pour synchroniser automatiquement tous les secrets TLS depuis les certificats dans OPS
# Usage: ./sync-all-certificates.sh [--sourceCluster "cluster-name"] [--sourceNS "certificates-ops"]
set -e
# Valeurs par défaut
SOURCE_CLUSTER="${SOURCE_CLUSTER:-}"
SOURCE_NS="${SOURCE_NS:-certificates-ops}"
# Parsing des arguments
while [[ $# -gt 0 ]]; do
case $1 in
--sourceCluster)
SOURCE_CLUSTER="$2"
shift 2
;;
--sourceNS)
SOURCE_NS="$2"
shift 2
;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --sourceCluster NAME Nom du cluster source (défaut: contexte actuel)"
echo " --sourceNS NAME Namespace source (défaut: certificates-ops)"
echo " --help, -h Afficher cette aide"
echo ""
echo "Ce script récupère automatiquement tous les certificats depuis OPS"
echo "et synchronise les secrets TLS vers les clusters appropriés."
exit 0
;;
*)
echo "❌ Option inconnue: $1"
echo "Utilisez --help pour voir l'aide"
exit 1
;;
esac
done
echo "=== Synchronisation automatique des secrets TLS ==="
echo "Source: $SOURCE_CLUSTER (namespace: $SOURCE_NS)"
echo ""
# Fonction pour déterminer automatiquement les namespaces cibles pour un certificat wildcard
# Détecte automatiquement tous les namespaces qui référencent ce secret dans leurs Ingress/IngressRoute
get_wildcard_target_namespaces() {
local cert_name=$1
local secret_name=$2
local target_cluster=$3
local env=""
# Extraire l'environnement depuis le nom du certificat
if [[ $cert_name =~ wildcard-dev-tls ]]; then
env="dev"
elif [[ $cert_name =~ wildcard-rct-tls ]]; then
env="rct"
elif [[ $cert_name =~ wildcard-prd-tls ]]; then
env="prd"
else
return 1
fi
echo " 🔍 Détection automatique des namespaces utilisant $secret_name..."
# Initialiser le tableau des namespaces cibles
WILDCARD_TARGET_NAMESPACES=()
# Récupérer tous les namespaces du cluster cible
local namespaces
if [ -z "$target_cluster" ]; then
namespaces=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}' 2>/dev/null)
else
namespaces=$(kubectl get namespaces --context="$target_cluster" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null)
fi
if [ -z "$namespaces" ]; then
echo " ⚠️ Aucun namespace trouvé dans le cluster $target_cluster"
return 1
fi
# Pour chaque namespace, vérifier s'il contient des Ingress ou IngressRoute qui référencent le secret
for ns in $namespaces; do
# Ignorer les namespaces système
if [[ "$ns" =~ ^(kube-system|kube-public|kube-node-lease|default)$ ]]; then
continue
fi
# Vérifier les Ingress (ressource standard Kubernetes)
local ingress_found=false
if [ -z "$target_cluster" ]; then
ingress_found=$(kubectl get ingress -n "$ns" -o json 2>/dev/null | jq -r --arg secret "$secret_name" '.items[] | select(.spec.tls[]?.secretName == $secret) | .metadata.name' 2>/dev/null | head -1)
else
ingress_found=$(kubectl get ingress -n "$ns" --context="$target_cluster" -o json 2>/dev/null | jq -r --arg secret "$secret_name" '.items[] | select(.spec.tls[]?.secretName == $secret) | .metadata.name' 2>/dev/null | head -1)
fi
# Vérifier les IngressRoute (Traefik CRD)
local ingressroute_found=false
if [ -z "$target_cluster" ]; then
ingressroute_found=$(kubectl get ingressroute -n "$ns" -o json 2>/dev/null | jq -r --arg secret "$secret_name" '.items[] | select(.spec.tls.secretName == $secret) | .metadata.name' 2>/dev/null | head -1)
else
ingressroute_found=$(kubectl get ingressroute -n "$ns" --context="$target_cluster" -o json 2>/dev/null | jq -r --arg secret "$secret_name" '.items[] | select(.spec.tls.secretName == $secret) | .metadata.name' 2>/dev/null | head -1)
fi
# Si le secret est référencé dans ce namespace, l'ajouter à la liste
if [ -n "$ingress_found" ] || [ -n "$ingressroute_found" ]; then
WILDCARD_TARGET_NAMESPACES+=("$ns")
echo "$ns (détecté via Ingress/IngressRoute)"
fi
done
if [ ${#WILDCARD_TARGET_NAMESPACES[@]} -eq 0 ]; then
echo " ⚠️ Aucun namespace n'utilise actuellement le secret $secret_name"
echo " Le secret sera synchronisé lors de la prochaine utilisation"
return 0 # Ne pas échouer, juste avertir
fi
return 0
}
# Fonction pour déterminer le cluster et namespace cible à partir du nom du certificat
# Format attendu: <app>-<env>-tls
# Exemple: homarr-dev-tls -> cluster-dev, namespace homarr-dev
# Pour les certificats wildcard, cette fonction détermine seulement le cluster
determine_target() {
local cert_name=$1
# Détecter les certificats wildcard
if [[ $cert_name =~ ^wildcard- ]]; then
# Pour les certificats wildcard, on détermine seulement le cluster
# Les namespaces seront déterminés par get_wildcard_target_namespaces
if [[ $cert_name =~ -dev-tls$ ]]; then
TARGET_CLUSTER="cluster-dev"
TARGET_NS="" # Sera rempli par get_wildcard_target_namespaces
IS_WILDCARD=true
elif [[ $cert_name =~ -rct-tls$ ]]; then
TARGET_CLUSTER="cluster-rct"
TARGET_NS=""
IS_WILDCARD=true
elif [[ $cert_name =~ -prd-tls$ ]]; then
TARGET_CLUSTER="cluster-prd"
TARGET_NS=""
IS_WILDCARD=true
else
echo "⚠️ Impossible de déterminer le cluster cible pour $cert_name"
return 1
fi
else
# Certificats normaux (non-wildcard)
IS_WILDCARD=false
# Extraire l'environnement (dev, rct, prd)
if [[ $cert_name =~ -dev-tls$ ]]; then
TARGET_CLUSTER="cluster-dev"
TARGET_NS="${cert_name%-tls}" # Garde le suffixe -dev
elif [[ $cert_name =~ -rct-tls$ ]]; then
TARGET_CLUSTER="cluster-rct"
TARGET_NS="${cert_name%-tls}" # Garde le suffixe -rct
elif [[ $cert_name =~ -prd-tls$ ]]; then
TARGET_CLUSTER="cluster-prd"
TARGET_NS="${cert_name%-tls}" # Garde le suffixe -prd
else
# Par défaut, essayer de deviner depuis le nom
# Si le nom contient "dev", utiliser cluster-dev
if [[ $cert_name == *"dev"* ]]; then
TARGET_CLUSTER="cluster-dev"
TARGET_NS="${cert_name%-tls}"
else
echo "⚠️ Impossible de déterminer le cluster cible pour $cert_name"
return 1
fi
fi
fi
return 0
}
# Déterminer le contexte à utiliser
echo "1. Détermination du contexte kubectl..."
if [ -z "$SOURCE_CLUSTER" ] || [ "$SOURCE_CLUSTER" == "in-cluster" ]; then
# Utiliser le contexte actuel
SOURCE_CLUSTER=$(kubectl config current-context 2>/dev/null)
if [ -z "$SOURCE_CLUSTER" ]; then
echo "❌ Erreur: Aucun contexte kubectl actuel trouvé"
echo " Contextes disponibles:"
kubectl config get-contexts -o name 2>/dev/null || echo " (aucun contexte trouvé)"
exit 1
fi
echo " Utilisation du contexte actuel: $SOURCE_CLUSTER"
else
# Vérifier que le contexte spécifié existe
if ! kubectl config get-contexts "$SOURCE_CLUSTER" &>/dev/null; then
echo "❌ Erreur: Le contexte kubectl '$SOURCE_CLUSTER' n'existe pas"
echo " Contextes disponibles:"
kubectl config get-contexts -o name 2>/dev/null || echo " (aucun contexte trouvé)"
exit 1
fi
echo " Utilisation du contexte: $SOURCE_CLUSTER"
fi
# Vérifier l'accès au cluster
echo "2. Vérification de l'accès au cluster..."
if [ -z "$SOURCE_CLUSTER" ]; then
# Pas de contexte spécifié, utiliser le contexte actuel
if ! kubectl get nodes &>/dev/null; then
echo "❌ Erreur: Impossible d'accéder au cluster"
echo " Vérifiez votre configuration kubectl et votre connexion réseau"
exit 1
fi
else
# Utiliser le contexte spécifié
if ! kubectl get nodes --context="$SOURCE_CLUSTER" &>/dev/null; then
echo "❌ Erreur: Impossible d'accéder au cluster $SOURCE_CLUSTER"
echo " Vérifiez votre configuration kubectl et votre connexion réseau"
exit 1
fi
fi
# Récupérer tous les certificats
echo "3. Récupération des certificats depuis $SOURCE_CLUSTER..."
if [ -z "$SOURCE_CLUSTER" ]; then
CERTIFICATES=$(kubectl get certificates -n "$SOURCE_NS" -o json 2>&1)
else
CERTIFICATES=$(kubectl get certificates -n "$SOURCE_NS" --context="$SOURCE_CLUSTER" -o json 2>&1)
fi
KUBECTL_EXIT_CODE=$?
if [ $KUBECTL_EXIT_CODE -ne 0 ]; then
echo "❌ Erreur lors de la récupération des certificats:"
echo "$CERTIFICATES" | head -5
exit 1
fi
if [ -z "$CERTIFICATES" ] || [ "$CERTIFICATES" == "null" ] || [ "$(echo "$CERTIFICATES" | jq -r '.items // empty')" == "" ]; then
echo "⚠️ Aucun certificat trouvé dans $SOURCE_NS sur $SOURCE_CLUSTER"
echo " Vérifiez que les certificats existent avec:"
if [ -z "$SOURCE_CLUSTER" ]; then
echo " kubectl get certificates -n $SOURCE_NS"
else
echo " kubectl get certificates -n $SOURCE_NS --context=$SOURCE_CLUSTER"
fi
exit 0
fi
# Vérifier que jq est installé
if ! command -v jq &> /dev/null; then
echo "❌ Erreur: jq n'est pas installé"
echo " Installez jq avec: sudo apt-get install jq (Ubuntu/Debian) ou brew install jq (macOS)"
exit 1
fi
# Extraire les certificats et leurs informations
CERT_COUNT=$(echo "$CERTIFICATES" | jq -r '.items | length' 2>/dev/null)
if [ -z "$CERT_COUNT" ] || [ "$CERT_COUNT" == "null" ] || [ "$CERT_COUNT" == "0" ]; then
echo "⚠️ Aucun certificat trouvé dans $SOURCE_NS sur $SOURCE_CLUSTER"
exit 0
fi
echo " Trouvé $CERT_COUNT certificat(s)"
echo ""
# Traiter chaque certificat
SUCCESS_COUNT=0
SKIP_COUNT=0
ERROR_COUNT=0
for i in $(seq 0 $((CERT_COUNT - 1))); do
CERT_NAME=$(echo "$CERTIFICATES" | jq -r ".items[$i].metadata.name")
SECRET_NAME=$(echo "$CERTIFICATES" | jq -r ".items[$i].spec.secretName")
CERT_NAMESPACE=$(echo "$CERTIFICATES" | jq -r ".items[$i].metadata.namespace")
IS_WILDCARD=false # Initialisation par défaut
# Si secretName n'est pas défini, utiliser le nom du certificat
if [ "$SECRET_NAME" == "null" ] || [ -z "$SECRET_NAME" ]; then
SECRET_NAME="$CERT_NAME"
fi
echo "📋 Traitement du certificat: $CERT_NAME"
echo " Secret: $SECRET_NAME"
# Déterminer le cluster et namespace cible
if ! determine_target "$CERT_NAME"; then
echo " ⚠️ Ignoré (impossible de déterminer la destination)"
SKIP_COUNT=$((SKIP_COUNT + 1))
echo ""
continue
fi
# Vérifier que le secret existe dans la source
if [ -z "$SOURCE_CLUSTER" ]; then
SECRET_CHECK_CMD="kubectl get secret \"$SECRET_NAME\" -n \"$SOURCE_NS\""
else
SECRET_CHECK_CMD="kubectl get secret \"$SECRET_NAME\" -n \"$SOURCE_NS\" --context=\"$SOURCE_CLUSTER\""
fi
if ! eval "$SECRET_CHECK_CMD" &>/dev/null; then
echo " ⚠️ Le secret $SECRET_NAME n'existe pas encore (certificat peut-être en cours de génération)"
SKIP_COUNT=$((SKIP_COUNT + 1))
echo ""
continue
fi
# Récupérer le secret une seule fois
TEMP_FILE=$(mktemp)
if [ -z "$SOURCE_CLUSTER" ]; then
SECRET_GET_CMD="kubectl get secret \"$SECRET_NAME\" -n \"$SOURCE_NS\" -o yaml"
else
SECRET_GET_CMD="kubectl get secret \"$SECRET_NAME\" -n \"$SOURCE_NS\" --context=\"$SOURCE_CLUSTER\" -o yaml"
fi
if ! eval "$SECRET_GET_CMD" > "$TEMP_FILE" 2>/dev/null; then
echo " ❌ Erreur: Impossible de récupérer le secret"
rm -f "$TEMP_FILE"
ERROR_COUNT=$((ERROR_COUNT + 1))
echo ""
continue
fi
# Traitement spécial pour les certificats wildcard
if [ "$IS_WILDCARD" = true ]; then
echo " 🌐 Certificat wildcard détecté"
# Obtenir la liste des namespaces cibles (détection automatique)
if ! get_wildcard_target_namespaces "$CERT_NAME" "$SECRET_NAME" "$TARGET_CLUSTER"; then
echo " ⚠️ Impossible de déterminer les namespaces cibles pour le wildcard"
rm -f "$TEMP_FILE"
SKIP_COUNT=$((SKIP_COUNT + 1))
echo ""
continue
fi
if [ ${#WILDCARD_TARGET_NAMESPACES[@]} -eq 0 ]; then
echo " Aucun namespace n'utilise actuellement ce certificat"
rm -f "$TEMP_FILE"
SKIP_COUNT=$((SKIP_COUNT + 1))
echo ""
continue
fi
echo " Destination: $TARGET_CLUSTER (namespaces: ${WILDCARD_TARGET_NAMESPACES[*]})"
# Copier le secret dans chaque namespace cible
for TARGET_NS in "${WILDCARD_TARGET_NAMESPACES[@]}"; do
echo " → Synchronisation vers namespace: $TARGET_NS"
# Créer une copie temporaire du fichier pour ce namespace
TEMP_FILE_NS=$(mktemp)
cp "$TEMP_FILE" "$TEMP_FILE_NS"
# Modifier les métadonnées pour ce namespace
sed -i.bak '/^ uid:/d' "$TEMP_FILE_NS" 2>/dev/null || true
sed -i.bak '/^ resourceVersion:/d' "$TEMP_FILE_NS" 2>/dev/null || true
sed -i.bak '/^ selfLink:/d' "$TEMP_FILE_NS" 2>/dev/null || true
sed -i.bak '/^ creationTimestamp:/d' "$TEMP_FILE_NS" 2>/dev/null || true
sed -i.bak "s/namespace: $SOURCE_NS/namespace: $TARGET_NS/" "$TEMP_FILE_NS" 2>/dev/null || true
rm -f "${TEMP_FILE_NS}.bak" 2>/dev/null || true
# Créer le namespace s'il n'existe pas
kubectl create namespace "$TARGET_NS" --context="$TARGET_CLUSTER" --dry-run=client -o yaml | kubectl apply --context="$TARGET_CLUSTER" -f - >/dev/null 2>&1 || true
# Appliquer le secret
if kubectl apply -f "$TEMP_FILE_NS" --context="$TARGET_CLUSTER" >/dev/null 2>&1; then
# Vérifier que le secret existe maintenant
if kubectl get secret "$SECRET_NAME" -n "$TARGET_NS" --context="$TARGET_CLUSTER" >/dev/null 2>&1; then
echo " ✅ Synchronisé avec succès dans $TARGET_NS"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
echo " ❌ Erreur: Le secret n'a pas été créé dans $TARGET_NS"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
else
echo " ❌ Erreur lors de l'application du secret dans $TARGET_NS"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# Nettoyage
rm -f "$TEMP_FILE_NS" "${TEMP_FILE_NS}.bak" 2>/dev/null || true
done
else
# Certificats normaux (non-wildcard) - comportement original
echo " Destination: $TARGET_CLUSTER (namespace: $TARGET_NS)"
echo " Synchronisation en cours..."
# Modifier les métadonnées
sed -i.bak '/^ uid:/d' "$TEMP_FILE" 2>/dev/null || true
sed -i.bak '/^ resourceVersion:/d' "$TEMP_FILE" 2>/dev/null || true
sed -i.bak '/^ selfLink:/d' "$TEMP_FILE" 2>/dev/null || true
sed -i.bak '/^ creationTimestamp:/d' "$TEMP_FILE" 2>/dev/null || true
sed -i.bak "s/namespace: $SOURCE_NS/namespace: $TARGET_NS/" "$TEMP_FILE" 2>/dev/null || true
rm -f "${TEMP_FILE}.bak" 2>/dev/null || true
# Créer le namespace s'il n'existe pas
kubectl create namespace "$TARGET_NS" --context="$TARGET_CLUSTER" --dry-run=client -o yaml | kubectl apply --context="$TARGET_CLUSTER" -f - >/dev/null 2>&1 || true
# Appliquer le secret
if kubectl apply -f "$TEMP_FILE" --context="$TARGET_CLUSTER" >/dev/null 2>&1; then
# Vérifier que le secret existe maintenant
if kubectl get secret "$SECRET_NAME" -n "$TARGET_NS" --context="$TARGET_CLUSTER" >/dev/null 2>&1; then
echo " ✅ Synchronisé avec succès"
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
echo " ❌ Erreur: Le secret n'a pas été créé"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
else
echo " ❌ Erreur lors de l'application du secret"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
# Nettoyage
rm -f "$TEMP_FILE" "${TEMP_FILE}.bak" 2>/dev/null || true
echo ""
done
# Résumé
echo "=== Résumé ==="
echo "✅ Synchronisés avec succès: $SUCCESS_COUNT"
echo "⚠️ Ignorés: $SKIP_COUNT"
echo "❌ Erreurs: $ERROR_COUNT"
echo ""
if [ $ERROR_COUNT -eq 0 ] && [ $SUCCESS_COUNT -gt 0 ]; then
echo "🎉 Toutes les synchronisations réussies !"
exit 0
elif [ $ERROR_COUNT -gt 0 ]; then
echo "⚠️ Certaines synchronisations ont échoué"
exit 1
else
echo " Aucune synchronisation effectuée"
exit 0
fi