Aller au contenu

Copy certificat let's encrypt existant d'un nas source vers un autre nas cible

Featured Replies

Posté(e)

Bonjour à Tous,

Désolé de pas être très actif sur le forum par manque de temps en particulier.
Mais je m'en remet à vos connaissances en la matières car je ne suis pas très compétent en commande Linux.

Mon objectif est d'utiliser le planificateur de tâches du Synology pour la tâche suivante : Copy certificat let's encrypt existant d'un Nas source vers un autre Nas cible.
J'ai 3 Nas Synology dont le 1er a un certificat "Let's Encrypt" qui se renouvelle régulièrement.
Jusqu'à présent, je suis obligé de copier manuellement le dernier certificat à jour du 1er vers les 2 autres.
Reprenez moi si je dis une bêtise, mais actuellement (sur les 2 autres NAS), il n'est pas possible de renouvellement (en mode auto tous les 3 mois) pour un même nom de domaine : Let's Encrypt ne le permet pas.

J'ai donc demandé à une IA de me créer un script pour effectuer cette action mais je n'ai aucune certitude de son bon fonctionnement, pire créer des erreurs ou disfonctionnement du Nas cible.
Ce script est ajouter sur le NAS source qui a le renouvellement automatique fonctionnel.
Il vérifie si un changement de version de certificat a eu lieu sur le Nas source, et seulement si, effectuer les vérifications sur le Nas cible pour renouvellement, redémarre les services et contrôle si toutes les tâches se sont appliquées sans erreurs.
Chose importante, ce script impose l'ouverture du service SSH sur les NAS. Je ne suis pas très chaud à faire cela pour les raisons évidentes de sécurité.
S'il est possible de modifier ce besoin ou bien de le sécurisé, je suis à votre écoute.

Je vous fourni le script ci-dessous, en espérant qu'une personne attentive et compétente prendra le temps de vérifier et me conseiller.
Je suis preneur de tout autre script existant pour cette tâche si déjà existante.
Vous remerciant par avance à ceux qui me lirons.
PS : ce qui est en couleur est à personnaliser selon votre environnement

En cas d'erreur, ne pas hésiter à me le faire savoir

Cordialement

****************************************************************************************************************
#!/bin/bash
# ============================================================
# "VOTRE_NOM DE DOMAINE" certificate synchronisation - V3
#
# Source:
# NAS 1
#
# Destination:
# IP_NAS_CIBLE
#
# DSM 7.2.2-72806 Update 6 (version du DSM à préciser - Idéalement identique aux 2 Nas)
#
# Fonctions:
# - auto-détection certificat via INFO
# - détection renouvellement SHA256
# - transfert SSH/SCP
# - import SYNO.Core.Certificate
# - prise en compte restart_httpd
# - vérification certificat installé
# - vérification certificat HTTPS réellement présenté
# - journalisation
# - notification DSM en cas d'erreur
#
# ============================================================
set -u
# ============================================================
# CONFIGURATION
# ============================================================
CERT_NAME="VOTRE_NOM_DE_DOMAINE"
NAS2="192.168.26.156"
NAS2_USER="root"
ARCHIVE_BASE="/usr/syno/etc/certificate/_archive"
INFO_FILE="${ARCHIVE_BASE}/INFO"
LOG_FILE="/var/log/VOTRE_NOM_DE_DOMAINE-cert-sync.log"
LOG_MAX_SIZE=2097152
STATE_DIR="/usr/local/etc"
STATE_FILE="${STATE_DIR}/VOTRE_NOM_DE_DOMAINE-cert-sync.sha256"
REMOTE_TMP="/tmp/VOTRE_NOM_DE_DOMAINE-cert-sync"
# HTTPS à vérifier sur NAS 2
HTTPS_HOST="IP_NAS_CIBLE"
HTTPS_PORT="PORT_NAS_CIBLE"
# Nombre de tentatives après import
VERIFY_RETRIES=12
# secondes entre deux tentatives
VERIFY_DELAY=5
# SSH
SSH_OPTS="
-o BatchMode=yes
-o ConnectTimeout=10
-o ServerAliveInterval=10
-o ServerAliveCountMax=3
"
# ============================================================
# LOG
# ============================================================
log()
{
echo "$(date '+%Y-%m-%d %H:%M:%S') | $*" >> "${LOG_FILE}"
}
# ============================================================
# NOTIFICATION
# ============================================================
notify()
{
TITLE="$1"
MESSAGE="$2"
# --------------------------------------------------------
# Notification DSM.
#
# synodsmnotify est conservé comme notification locale.
# Selon DSM/configuration, les chaînes libres peuvent être
# ignorées car DSM 7 documente normalement des clés I18N.
# --------------------------------------------------------
if [ -x /usr/syno/bin/synodsmnotify ]; then
/usr/syno/bin/synodsmnotify \
@administrators \
"${TITLE}" \
"${MESSAGE}" \
>/dev/null 2>&1 || true
fi
}
# ============================================================
# ERREUR
# ============================================================
error_exit()
{
MSG="$1"
log "============================================================"
log "ERREUR | ${MSG}"
log "============================================================"
notify \
"Certificat ${CERT_NAME}" \
"Erreur synchronisation NAS 2 : ${MSG}"
exit 1
}
# ============================================================
# ROTATION LOG
# ============================================================
mkdir -p "$(dirname "${LOG_FILE}")"
if [ -f "${LOG_FILE}" ]; then
LOG_SIZE=$(wc -c < "${LOG_FILE}" 2>/dev/null || echo 0)
if [ "${LOG_SIZE}" -ge "${LOG_MAX_SIZE}" ]; then
mv "${LOG_FILE}" "${LOG_FILE}.1" 2>/dev/null || true
fi
fi
touch "${LOG_FILE}" || exit 1
chmod 600 "${LOG_FILE}"
# ============================================================
# DEBUT
# ============================================================
log "============================================================"
log "Début synchronisation ${CERT_NAME}"
log "============================================================"
# ============================================================
# ROOT
# ============================================================
if [ "$(id -u)" != "0" ]; then
error_exit "Le script doit être exécuté en root."
fi
# ============================================================
# VERIFICATION COMMANDES
# ============================================================
for CMD in jq openssl sha256sum ssh scp timeout; do
if ! command -v "${CMD}" >/dev/null 2>&1; then
error_exit "Commande absente : ${CMD}"
fi
done
# ============================================================
# AUTO-DETECTION CERTIFICAT NAS 1
# ============================================================
if [ ! -f "${INFO_FILE}" ]; then
error_exit "INFO introuvable : ${INFO_FILE}"
fi
SOURCE_CERT_ID=$(
jq -r \
--arg CERT "${CERT_NAME}" \
'
to_entries[]
| select(
any(
.value.services[]?;
.display_name == $CERT
)
)
| .key
' "${INFO_FILE}" |
head -n 1
)
if [ -z "${SOURCE_CERT_ID}" ] ||
[ "${SOURCE_CERT_ID}" = "null" ]; then
error_exit \
"Certificat ${CERT_NAME} introuvable sur NAS 1."
fi
SOURCE_DIR="${ARCHIVE_BASE}/${SOURCE_CERT_ID}"
log "Certificat NAS 1 : ${SOURCE_CERT_ID}"
log "Répertoire : ${SOURCE_DIR}"
# ============================================================
# VERIFICATION FICHIERS SOURCE
# ============================================================
for FILE in cert.pem chain.pem privkey.pem; do
if [ ! -s "${SOURCE_DIR}/${FILE}" ]; then
error_exit \
"Fichier source absent ou vide : ${FILE}"
fi
done
# ============================================================
# VALIDATION CERTIFICAT
# ============================================================
if ! openssl x509 \
-in "${SOURCE_DIR}/cert.pem" \
-noout >/dev/null 2>&1; then
error_exit "Certificat source invalide."
fi
SOURCE_SUBJECT=$(
openssl x509 \
-in "${SOURCE_DIR}/cert.pem" \
-noout -subject
)
SOURCE_ISSUER=$(
openssl x509 \
-in "${SOURCE_DIR}/cert.pem" \
-noout -issuer
)
SOURCE_ENDDATE=$(
openssl x509 \
-in "${SOURCE_DIR}/cert.pem" \
-noout -enddate
)
SOURCE_SERIAL=$(
openssl x509 \
-in "${SOURCE_DIR}/cert.pem" \
-noout -serial
)
log "Sujet : ${SOURCE_SUBJECT}"
log "Émetteur : ${SOURCE_ISSUER}"
log "Expiration : ${SOURCE_ENDDATE}"
log "Serial : ${SOURCE_SERIAL}"
# ============================================================
# EMPREINTE
# ============================================================
CURRENT_HASH=$(
{
sha256sum "${SOURCE_DIR}/cert.pem"
sha256sum "${SOURCE_DIR}/chain.pem"
sha256sum "${SOURCE_DIR}/privkey.pem"
} |
sha256sum |
awk '{print $1}'
)
log "Empreinte actuelle : ${CURRENT_HASH}"
# ============================================================
# DETECTION RENOUVELLEMENT
# ============================================================
if [ -f "${STATE_FILE}" ]; then
OLD_HASH=$(cat "${STATE_FILE}" 2>/dev/null)
log "Empreinte précédente : ${OLD_HASH}"
if [ "${CURRENT_HASH}" = "${OLD_HASH}" ]; then
log "Aucun changement détecté."
log "Fin."
log "============================================================"
exit 0
fi
log "Nouveau certificat détecté."
else
log "Première exécution : aucune empreinte précédente."
fi
# ============================================================
# SSH NAS 2
# ============================================================
log "Connexion SSH vers ${NAS2}..."
if ! ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"echo SSH_OK" 2>/dev/null |
grep -q "SSH_OK"; then
error_exit \
"Connexion SSH impossible vers ${NAS2}."
fi
log "SSH OK."
# ============================================================
# AUTO-DETECTION CERTIFICAT NAS 2
# ============================================================
REMOTE_CERT_ID=$(
ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"jq -r --arg CERT '${CERT_NAME}' '
to_entries[]
| select(
any(
.value.services[]?;
.display_name == \$CERT
)
)
| .key
' /usr/syno/etc/certificate/_archive/INFO" \
2>/dev/null |
head -n 1
)
if [ -z "${REMOTE_CERT_ID}" ] ||
[ "${REMOTE_CERT_ID}" = "null" ]; then
error_exit \
"Certificat ${CERT_NAME} introuvable sur NAS 2."
fi
REMOTE_CERT_DIR="${ARCHIVE_BASE}/${REMOTE_CERT_ID}"
log "Certificat NAS 2 : ${REMOTE_CERT_ID}"
log "Répertoire : ${REMOTE_CERT_DIR}"
# ============================================================
# PREPARATION TEMPORAIRE
# ============================================================
log "Préparation transfert..."
if ! ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"rm -rf '${REMOTE_TMP}' &&
mkdir -m 700 '${REMOTE_TMP}'"; then
error_exit \
"Impossible de créer le répertoire temporaire NAS 2."
fi
# ============================================================
# NETTOYAGE DISTANT EN CAS D'ERREUR
# ============================================================
cleanup_remote()
{
ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"rm -rf '${REMOTE_TMP}'" \
>/dev/null 2>&1 || true
}
# ============================================================
# COPIE
# ============================================================
log "Copie cert.pem..."
if ! scp ${SSH_OPTS} \
"${SOURCE_DIR}/cert.pem" \
"${NAS2_USER}@${NAS2}:${REMOTE_TMP}/cert.pem"; then
cleanup_remote
error_exit "Copie cert.pem échouée."
fi
log "Copie chain.pem..."
if ! scp ${SSH_OPTS} \
"${SOURCE_DIR}/chain.pem" \
"${NAS2_USER}@${NAS2}:${REMOTE_TMP}/chain.pem"; then
cleanup_remote
error_exit "Copie chain.pem échouée."
fi
log "Copie privkey.pem..."
if ! scp ${SSH_OPTS} \
"${SOURCE_DIR}/privkey.pem" \
"${NAS2_USER}@${NAS2}:${REMOTE_TMP}/privkey.pem"; then
cleanup_remote
error_exit "Copie privkey.pem échouée."
fi
ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"chmod 600 '${REMOTE_TMP}/privkey.pem'"
log "Copie terminée."
# ============================================================
# IMPORT DSM
# ============================================================
log "Import certificat dans DSM..."
REMOTE_RESULT=$(
ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
bash <<REMOTE_SCRIPT
set -u
CERT_ID="${REMOTE_CERT_ID}"
TMP="${REMOTE_TMP}"
for FILE in cert.pem chain.pem privkey.pem; do
if [ ! -s "\${TMP}/\${FILE}" ]; then
echo '{"success":false,"error":"Fichier manquant"}'
rm -rf "\${TMP}"
exit 1
fi
done
# Validation certificat
if ! openssl x509 \
-in "\${TMP}/cert.pem" \
-noout >/dev/null 2>&1; then
echo '{"success":false,"error":"Certificat invalide"}'
rm -rf "\${TMP}"
exit 1
fi
# ------------------------------------------------------------
# Import DSM
# ------------------------------------------------------------
RESULT=\$(
/usr/syno/bin/synowebapi \
--exec-fastwebapi \
api="SYNO.Core.Certificate" \
method="import" \
version="1" \
key_tmp="\"\${TMP}/privkey.pem\"" \
cert_tmp="\"\${TMP}/cert.pem\"" \
inter_cert_tmp="\"\${TMP}/chain.pem\"" \
id="\"\${CERT_ID}\"" \
desc="\"${CERT_NAME}\"" \
2>&1
)
RET=\$?
echo "\${RESULT}"
rm -rf "\${TMP}"
exit \${RET}
REMOTE_SCRIPT
)
REMOTE_RET=$?
log "Réponse API : ${REMOTE_RESULT}"
if [ "${REMOTE_RET}" -ne 0 ]; then
error_exit \
"API DSM : code retour ${REMOTE_RET}."
fi
# ============================================================
# RESULTAT API
# ============================================================
API_SUCCESS=$(
echo "${REMOTE_RESULT}" |
jq -r '.success // false' 2>/dev/null
)
if [ "${API_SUCCESS}" != "true" ]; then
error_exit \
"DSM n'a pas confirmé l'import."
fi
log "Import DSM réussi."
# ============================================================
# DETECTION RESTART HTTPD
# ============================================================
RESTART_HTTPD=$(
echo "${REMOTE_RESULT}" |
jq -r '.data.restart_httpd // .restart_httpd // false' \
2>/dev/null
)
log "restart_httpd : ${RESTART_HTTPD}"
# ============================================================
# SI DSM DEMANDE RESTART HTTP
# ============================================================
if [ "${RESTART_HTTPD}" = "true" ]; then
log "DSM demande un redémarrage/rechargement HTTP."
# --------------------------------------------------------
# On ne tue PAS nginx directement.
#
# On laisse DSM gérer ses services.
# --------------------------------------------------------
ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"/usr/syno/bin/synowebapi \
--exec-fastwebapi \
api='SYNO.Core.Web.DSM' \
method='restart' \
version='1' \
>/dev/null 2>&1 || true" \
>/dev/null 2>&1 || true
log "Commande de rechargement HTTP envoyée."
else
log "DSM n'indique pas de redémarrage HTTP nécessaire."
fi
# ============================================================
# ATTENTE DU SERVICE HTTPS
# ============================================================
log "Attente disponibilité HTTPS NAS 2..."
HTTPS_READY="false"
for i in $(seq 1 "${VERIFY_RETRIES}"); do
if timeout 5 bash -c \
"</dev/tcp/${HTTPS_HOST}/${HTTPS_PORT}" \
>/dev/null 2>&1; then
HTTPS_READY="true"
log "HTTPS disponible après ${i} tentative(s)."
break
fi
log "HTTPS indisponible - tentative ${i}/${VERIFY_RETRIES}"
sleep "${VERIFY_DELAY}"
done
if [ "${HTTPS_READY}" != "true" ]; then
error_exit \
"HTTPS ${HTTPS_HOST}:${HTTPS_PORT} ne répond pas après import."
fi
# ============================================================
# VERIFICATION CERTIFICAT DANS ARCHIVE DSM
# ============================================================
log "Vérification certificat dans _archive..."
REMOTE_SERIAL=$(
ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"openssl x509 \
-in '${REMOTE_CERT_DIR}/cert.pem' \
-noout -serial" \
2>/dev/null
)
if [ "${REMOTE_SERIAL}" != "${SOURCE_SERIAL}" ]; then
error_exit \
"Serial du certificat installé différent."
fi
log "Serial _archive : OK."
# ============================================================
# VERIFICATION SHA256 CERTIFICAT PUBLIC
# ============================================================
SOURCE_CERT_HASH=$(
sha256sum "${SOURCE_DIR}/cert.pem" |
awk '{print $1}'
)
REMOTE_CERT_HASH=$(
ssh ${SSH_OPTS} \
"${NAS2_USER}@${NAS2}" \
"sha256sum '${REMOTE_CERT_DIR}/cert.pem'" |
awk '{print $1}'
)
if [ "${SOURCE_CERT_HASH}" != "${REMOTE_CERT_HASH}" ]; then
error_exit \
"SHA256 du certificat _archive différent."
fi
log "SHA256 _archive : OK."
# ============================================================
# VERIFICATION CERTIFICAT PRESENTE PAR HTTPS
#
# On interroge le NAS 2 directement.
#
# -servername = VOTRE_NOM_DE_DOMAINE.me
# permet de tester le certificat du bon virtual host.
# ============================================================
log "Vérification certificat réellement présenté par HTTPS..."
REMOTE_HTTPS_SERIAL=$(
echo |
timeout 15 openssl s_client \
-connect "${HTTPS_HOST}:${HTTPS_PORT}" \
-servername "${CERT_NAME}" \
-showcerts \
2>/dev/null |
openssl x509 \
-noout \
-serial 2>/dev/null
)
if [ -z "${REMOTE_HTTPS_SERIAL}" ]; then
error_exit \
"Impossible de récupérer le certificat présenté par HTTPS."
fi
log "Serial HTTPS : ${REMOTE_HTTPS_SERIAL}"
if [ "${REMOTE_HTTPS_SERIAL}" != "${SOURCE_SERIAL}" ]; then
error_exit \
"Le certificat présenté par HTTPS ne correspond pas au certificat source."
fi
log "Certificat HTTPS : OK."
# ============================================================
# VERIFICATION DATE D'EXPIRATION HTTPS
# ============================================================
REMOTE_HTTPS_ENDDATE=$(
echo |
timeout 15 openssl s_client \
-connect "${HTTPS_HOST}:${HTTPS_PORT}" \
-servername "${CERT_NAME}" \
2>/dev/null |
openssl x509 \
-noout \
-enddate 2>/dev/null
)
log "Expiration HTTPS : ${REMOTE_HTTPS_ENDDATE}"
# ============================================================
# SAUVEGARDE ETAT
# ============================================================
mkdir -p "${STATE_DIR}"
echo "${CURRENT_HASH}" > "${STATE_FILE}"
chmod 600 "${STATE_FILE}"
log "Nouvelle empreinte enregistrée."
# ============================================================
# SUCCES
# ============================================================
log "============================================================"
log "SUCCES"
log "Certificat ${CERT_NAME} synchronisé vers NAS 2."
log "Certificat DSM : ${REMOTE_CERT_ID}"
log "Serial : ${SOURCE_SERIAL}"
log "HTTPS : vérifié"
log "============================================================"
notify \
"Certificat ${CERT_NAME}" \
"Certificat renouvelé et vérifié sur le NAS 2."
exit 0
Posté(e)
il y a 10 minutes, philou65 a dit :

il n'est pas possible de renouvellement (en mode auto tous les 3 mois) pour un même nom de domaine : Let's Encrypt ne le permet pas.

Ah bon ? Je fais ça sans problème sur plusieurs NAS. Avez-vous au moins essayé ?

Posté(e)
  • Auteur

Bonjour Mic13710,

Question pertinente mais oui, évidemment.
Si vous avez un mode opératoire, je suis preneur.
Je précise que les 3 Nas sont sont sur le même environnement réseau et nom de domaine identique sous OVH (pas un nom de domaine avec extension Synology).

Pour ne pas le citer, mais à l'époque (2020), dans un sujet déjà évoqué, un membre tout à fait compétent (beaucoup plus que moi) précisait que cela n'était pas possible (dans ma configuration).
D'où, la recherche d'un script permettant de contourner cette restriction.

Rejoindre la conversation

Vous pouvez publier maintenant et vous inscrire plus tard. Si vous avez un compte, connectez-vous maintenant pour publier avec votre compte.

Invité
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Répondre à ce sujet…

Account

Navigation

Rechercher

Rechercher

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.