#!/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: --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