Files
SSL-Sync--Nginx-Proxy-Manag…/README.md
T
2026-04-12 00:32:34 +03:00

312 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
### ASSISTANT
# 🔄 SSL Sync: Nginx Proxy Manager → Home Assistant
> 📖 **Описание**
> Автоматическая синхронизация Let's Encrypt сертификатов с сервера **Nginx Proxy Manager** в **Home Assistant** через SSH и cron.
> Оптимизировано для работы через туннель Keenetic, включает проверку целостности, дедупликацию (перезапуск только при реальном изменении), ротацию лога и безопасный перезапуск HA.
---
## 🌐 Архитектура решения
| Компонент | IP / Путь | Роль |
|-----------|-----------|------|
| **Nginx Proxy Manager** | `192.168.1.2` | Хост-сервер, хранит сертификаты |
| **Home Assistant** | `192.168.31.10` | VM-клиент, принимает сертификаты в `/ssl/` |
| **Сеть** | Туннель Keenetic | Обеспечивает маршрутизацию между подсетями `192.168.1.0/24` и `192.168.31.0/24` |
| **Цель** | `/opt/npm/letsencrypt/live/npm-44/``root@192.168.31.10:/ssl/` | Ежедневная авто-синхронизация |
---
## ✅ Требования
- Доступ к **хосту NPM** по SSH (скрипт выполняется в базовой ОС, а не в контейнере)
- SSH-доступ к HA от пользователя `root` (включен в HA OS или через Add-on `SSH & Web Terminal`)
- Установленные `openssh-client`, `cron`, `bash`, `openssl` на хосте NPM
- В `configuration.yaml` HA указана папка `/ssl` для сертификатов
---
## 🛠 Пошаговая настройка
### 1. 🔑 Настройка беспарольного SSH
Выполните на **хосте NPM** (`192.168.1.2`):
```bash
# 1. Генерация ключа (если отсутствует)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_npm_ha -N "" -C "npm-to-ha"
# 2. Копирование публичного ключа на HA
ssh-copy-id -i ~/.ssh/id_ed25519_npm_ha.pub root@192.168.31.10
# 3. Фиксируем fingerprint хоста (вместо StrictHostKeyChecking=no)
ssh-keyscan -H 192.168.31.10 >> ~/.ssh/known_hosts
# 4. Проверка подключения
ssh -i ~/.ssh/id_ed25519_npm_ha root@192.168.31.10 echo "✅ SSH OK"
```
> ⚠️ **Почему шаг 3 важен:** `StrictHostKeyChecking=no` отключает защиту от MITM-атак даже внутри LAN. Однократный `ssh-keyscan` безопаснее и не требует интерактивного подтверждения при каждом запуске.
> ⚠️ **HA OS**: Если используется Home Assistant OS, убедитесь, что SSH включён через `Settings → Add-ons → SSH & Web Terminal`, а ключ добавлен в поле `Authorized Keys`.
---
### 2. 📜 Создание скрипта синхронизации
```bash
sudo mkdir -p /opt/npm/scripts
sudo nano /opt/npm/scripts/sync_ssl_to_ha.sh
```
Вставьте следующий код:
```bash
#!/bin/bash
# === КОНФИГУРАЦИЯ ===
FULLCHAIN="/opt/npm/letsencrypt/live/npm-44/fullchain.pem"
PRIVKEY="/opt/npm/letsencrypt/live/npm-44/privkey.pem"
HA_USER="root"
HA_IP="192.168.31.10"
DEST_DIR="/ssl"
SSH_KEY="/root/.ssh/id_ed25519_npm_ha"
LOG="/var/log/ha_ssl_sync.log"
MAX_LOG_LINES=500 # Ротация: хранить не более N строк лога
# Флаги для уведомлений (раскомментируйте и настройте при необходимости)
# HA_TOKEN="your_long_lived_token"
# HA_NOTIFY_URL="http://192.168.31.10:8123/api/services/notify/persistent_notification"
SSH_OPTS="-i $SSH_KEY -o StrictHostKeyChecking=yes -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3"
# === ФУНКЦИИ ===
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') | $1" >> "$LOG"; }
rotate_log() {
local lines
lines=$(wc -l < "$LOG" 2>/dev/null || echo 0)
if [[ $lines -gt $MAX_LOG_LINES ]]; then
tail -n "$MAX_LOG_LINES" "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG"
fi
}
validate_cert() {
# Проверяем, что fullchain содержит валидный PEM-сертификат и не просрочен
if ! openssl x509 -in "$FULLCHAIN" -noout -checkend 0 2>/dev/null; then
log "❌ Сертификат невалиден или уже просрочен: $FULLCHAIN"
return 1
fi
# Проверяем соответствие ключа сертификату
local cert_mod privkey_mod
cert_mod=$(openssl x509 -noout -modulus -in "$FULLCHAIN" 2>/dev/null | md5sum)
privkey_mod=$(openssl rsa -noout -modulus -in "$PRIVKEY" 2>/dev/null | md5sum)
if [[ "$cert_mod" != "$privkey_mod" ]]; then
log "❌ Приватный ключ не соответствует сертификату!"
return 1
fi
return 0
}
cert_changed() {
# Получаем серийный номер текущего сертификата на HA и сравниваем с локальным
local local_serial ha_serial
local_serial=$(openssl x509 -noout -serial -in "$FULLCHAIN" 2>/dev/null | cut -d= -f2)
ha_serial=$(ssh $SSH_OPTS "$HA_USER@$HA_IP" \
"openssl x509 -noout -serial -in $DEST_DIR/fullchain.pem 2>/dev/null | cut -d= -f2" 2>/dev/null)
[[ "$local_serial" != "$ha_serial" ]]
}
# notify_ha() {
# curl -s -X POST "$HA_NOTIFY_URL" \
# -H "Authorization: Bearer $HA_TOKEN" \
# -H "Content-Type: application/json" \
# -d "{\"message\": \"$1\", \"title\": \"SSL Sync\"}" > /dev/null
# }
# === ОСНОВНАЯ ЛОГИКА ===
rotate_log
log "=== Старт синхронизации ==="
# 1. Проверка исходных файлов
if [[ ! -f "$FULLCHAIN" || ! -f "$PRIVKEY" ]]; then
log "❌ Исходные сертификаты не найдены: $FULLCHAIN или $PRIVKEY"
exit 1
fi
# 2. Валидация сертификата перед копированием
if ! validate_cert; then
exit 1
fi
# 3. Проверка: изменился ли сертификат?
if ! cert_changed; then
log "✅ Сертификат не изменился — синхронизация не требуется."
exit 0
fi
log "🆕 Обнаружен новый сертификат, начинаем синхронизацию..."
# 4. Копирование файлов
scp $SSH_OPTS "$FULLCHAIN" "$HA_USER@$HA_IP:$DEST_DIR/fullchain.pem" >> "$LOG" 2>&1
SCP1=$?
scp $SSH_OPTS "$PRIVKEY" "$HA_USER@$HA_IP:$DEST_DIR/privkey.pem" >> "$LOG" 2>&1
SCP2=$?
# 5. Перезапуск только при успешном копировании обоих файлов
if [[ $SCP1 -eq 0 && $SCP2 -eq 0 ]]; then
log "✅ Файлы успешно скопированы"
ssh $SSH_OPTS "$HA_USER@$HA_IP" "chmod 600 $DEST_DIR/privkey.pem && ha core restart" >> "$LOG" 2>&1
log "🔄 Команда рестарта HA отправлена"
# notify_ha "Сертификат обновлён и HA перезапущен"
else
log "❌ Ошибка копирования (fullchain=$SCP1, privkey=$SCP2). Рестарт отменён."
# notify_ha "⚠️ Ошибка синхронизации SSL — рестарт HA отменён"
exit 1
fi
```
---
### 3. 🔐 Настройка прав и логирования
```bash
sudo chmod +x /opt/npm/scripts/sync_ssl_to_ha.sh
sudo touch /var/log/ha_ssl_sync.log
sudo chown root:root /opt/npm/scripts/sync_ssl_to_ha.sh
sudo chmod 700 /opt/npm/scripts/sync_ssl_to_ha.sh # только root, без лишних прав
```
> ️ `chmod 700` вместо `750` — скрипт содержит путь к SSH-ключу и должен быть доступен только владельцу.
---
### 4. ⏰ Автоматизация через Cron
```bash
sudo crontab -e
```
Добавьте строку:
```cron
# Ежедневно в 04:15 (через 15 мин после планового обновления Let's Encrypt в NPM)
15 4 * * * /bin/bash /opt/npm/scripts/sync_ssl_to_ha.sh
```
> 💡 **Почему 04:15?** NPM запускает проверку обновления сертификатов в `04:00`. Задержка в 15 минут гарантирует, что свежие файлы уже записаны на диск.
> 💡 **Альтернатива:** если NPM поддерживает deploy-хуки (например через `post-renewal`), можно запускать скрипт напрямую по событию обновления, а не по расписанию — это надёжнее.
---
### 5. ⚙️ Конфигурация Home Assistant
В `configuration.yaml` убедитесь, что указан путь к `/ssl`:
```yaml
http:
ssl_certificate: /ssl/fullchain.pem
ssl_key: /ssl/privkey.pem
# server_port: 443 # Раскомментируйте, если HA слушает 443 напрямую
use_x_forwarded_for: true
trusted_proxies:
- 192.168.1.2
- 127.0.0.1
```
После первого изменения перезапустите HA вручную:
```bash
ssh root@192.168.31.10 "ha core restart"
```
---
## 🧪 Тестирование
1. **Ручной запуск:**
```bash
sudo /opt/npm/scripts/sync_ssl_to_ha.sh
```
2. **Проверка лога:**
```bash
tail -n 20 /var/log/ha_ssl_sync.log
```
3. **Проверка срока действия сертификата на HA:**
```bash
ssh root@192.168.31.10 "openssl x509 -noout -dates -in /ssl/fullchain.pem"
```
4. **Валидация в HA:**
- Откройте `https://<ваш-домен-или-ip>:443`
- Убедитесь, что браузер не ругается на сертификат
- В `Настройки → Система → Журналы` не должно быть ошибок SSL
5. **Принудительный тест синхронизации** (временно удалить сертификат на HA):
```bash
ssh root@192.168.31.10 "rm /ssl/fullchain.pem"
sudo /opt/npm/scripts/sync_ssl_to_ha.sh
```
---
## 🐛 Устранение неполадок
| Симптом | Причина | Решение |
|--------|---------|---------|
| `Permission denied` при `scp` | Ключ не добавлен или неправильные права | Проверьте `~/.ssh/authorized_keys` на HA и `chmod 600 ~/.ssh/id_ed25519_npm_ha` |
| `Host key verification failed` | Fingerprint не добавлен | Выполните `ssh-keyscan -H 192.168.31.10 >> ~/.ssh/known_hosts` на хосте NPM |
| `❌ Приватный ключ не соответствует` | Рассинхронизация файлов в NPM | Проверьте целостность файлов в `/opt/npm/letsencrypt/live/npm-44/` |
| `ha: command not found` | CLI не в PATH или не установлен | Используйте `docker restart homeassistant` или `systemctl restart home-assistant@homeassistant` |
| HA не стартует после синхронизации | Неправильные права на `privkey.pem` | Скрипт уже делает `chmod 600`. Если ошибка — проверьте, не перезаписывает ли файл Supervisor |
| Cron не запускает скрипт | Отсутствие полных путей или переменных окружения | В cron всегда указывайте `/bin/bash /полный/путь/скрипт.sh` |
| Таймаут по SSH | Нестабильный туннель Keenetic | Параметры `ConnectTimeout=10`, `ServerAliveInterval=5`, `ServerAliveCountMax=3` уже заданы в `SSH_OPTS` |
| Лог растёт бесконечно | Нет ротации | Ротация встроена в скрипт (`MAX_LOG_LINES`). Также можно добавить `logrotate` |
---
## 🔒 Безопасность и рекомендации
- ✅ Ключ SSH хранится с правами `600`, скрипт — с правами `700`. Не передавайте их третьим лицам.
- ✅ `StrictHostKeyChecking=yes` — fingerprint HA зафиксирован в `known_hosts`, MITM-атаки невозможны.
- ✅ Скрипт проверяет валидность и соответствие ключа сертификату **до** копирования.
- ✅ Перезапуск HA происходит **только** при успешном копировании обоих файлов **и** только при реальном изменении сертификата.
- ✅ Встроенная ротация лога — файл не вырастет бесконечно.
- 🔔 Для мониторинга раскомментируйте блок `notify_ha` и добавьте `HA_TOKEN` — будете получать уведомления прямо в интерфейсе HA (или настройте Telegram через `notify.telegram`).
- 🔄 Раз в квартал проверяйте актуальность ключа SSH и ротацию `authorized_keys` на HA.
---
## 📦 Структура файлов
```
/opt/npm/scripts/
└── sync_ssl_to_ha.sh # Основной скрипт
/root/.ssh/
├── id_ed25519_npm_ha # Приватный SSH-ключ (chmod 600)
├── id_ed25519_npm_ha.pub # Публичный ключ
└── known_hosts # Fingerprint HA (StrictHostKeyChecking=yes)
/var/log/
└── ha_ssl_sync.log # Лог операций (авторотация)
```
---
## 📋 Сводка изменений относительно v1.0.0
| Что изменилось | Почему |
|----------------|--------|
| `StrictHostKeyChecking=no` → `yes` + `ssh-keyscan` | Защита от MITM даже во внутренней сети |
| Добавлена валидация сертификата (`openssl x509 -checkend`) | Не синхронизировать просроченный или повреждённый файл |
| Добавлена проверка соответствия ключа сертификату | Исключить ситуацию рассинхронизации файлов в NPM |
| Добавлена дедупликация по серийному номеру | HA не перезапускается, если сертификат не менялся |
| Добавлена ротация лога (`MAX_LOG_LINES`) | Лог не вырастет до бесконечности |
| `chmod 750` → `chmod 700` | Скрипт с путями к ключам должен быть доступен только `root` |
| `ServerAliveCountMax=3` добавлен в `SSH_OPTS` | Надёжнее при нестабильном туннеле Keenetic |
| Добавлена заготовка `notify_ha()` | Уведомления в HA при успехе и ошибке — раскомментировать и настроить |
---
📝 *Документация подготовлена для размещения в Gitea/Wiki. Версия: `1.1.0` | Обновлено: `2026-04-12`*
🔗 *При изменении архитектуры сети или пути к сертификатам обновите переменные в шапке скрипта.*