diff --git a/Add Let's Encrypt Certificate для провайдера reg.ru.md b/Add Let's Encrypt Certificate для провайдера reg.ru.md new file mode 100644 index 0000000..bf4f748 --- /dev/null +++ b/Add Let's Encrypt Certificate для провайдера reg.ru.md @@ -0,0 +1,56 @@ +# Инструкция по созданию Let's Encrypt сертификата с DNS Challenge для провайдера reg.ru в Nginx Proxy Manager + +--- + +## Предпосылки +- Доступ к Nginx Proxy Manager (NPM) +- Доступ к аккаунту reg.ru с правами управления DNS-записями +- API-ключ для управления DNS в reg.ru (если есть автоматическая интеграция) +- Нужно получить сертификат для `*.dfv24.com` (wildcard сертификат) + +--- + +## Шаг 1. Получение API-ключа для reg.ru + +1. Войдите в панель управления reg.ru +2. Перейдите в раздел управления API (если поддерживается) +3. Создайте или найдите API-ключ с правом редактирования DNS записей +4. Сохраните API-ключ и секрет (Client ID и API Token) + +--- + +## Шаг 2. Настройка Nginx Proxy Manager для использования DNS Challenge reg.ru + +1. В админке NPM перейдите в **SSL Certificates → Add SSL Certificate** +2. Выберите **Let's Encrypt** -> **DNS Challenge** +3. В поле **Provider** выберите `reg_ru` или `custom` (если провайдера нет, потребуется писать скрипт) +4. В поля API впишите необходимые параметры: + - Client ID + - API Token +5. В поле **Domain Names** укажите: + `*.dfv24.com` (для wildcard сертификата) + и основной домен `dfv24.com` +6. Включите остальные опции (Terms of Service, Email) +7. Нажмите **Save** для запроса сертификата +8. NPM автоматически добавит DNS TXT-записи для подтверждения владения доменом через API reg.ru + +--- + +## Шаг 3. Проверка и автоматическое продление + +- После успешного создания сертификата NPM будет автоматически обновлять его через DNS Challenge. +- Для успешного продления важно, чтобы API-ключ был действующим, а NPM имел доступ к DNS управлению. + +--- + +## Если в NPM нет готовой интеграции с reg.ru + +- Используйте внешний скрипт для обновления TXT записей DNS в reg.ru, настраиваемый в NPM через **Custom DNS Provider**. +- Нужна настройка curl-запросов к API reg.ru для добавления/удаления TXT записей. + +--- + +# Итог + +Для wildcard сертификатов Let's Encrypt у reg.ru необходимо использовать DNS Challenge, используя API провайдера для автоматического управления DNS записями. +В Nginx Proxy Manager настройте DNS Challenge с учётом особенностей reg.ru для бесшовного получения и продления сертификатов. diff --git a/README.md b/README.md index 72d2014..147e8cf 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,532 @@ -# Подробная инструкция по настройке Nginx Proxy Manager с одним глобальным SSL сертификатом для всех доменов dfv24.com +# Руководство по использованию скриптов для управления SSL сертификатами Let's Encrypt с DNS-валидацией через API reg.ru -## Предпосылки -- Установлен и запущен [Nginx Proxy Manager](http://192.168.10.14:81/) -- Основной домен: dfv24.com -- Хостинг и DNS записи домена находятся на reg.ru -- Нужно использовать один SSL сертификат (например, wildcard) для всех поддоменов dfv24.com +## Содержание +1. [Введение](#введение) +2. [Требования](#требования) +3. [Установка зависимостей](#установка-зависимостей) +4. [Настройка](#настройка) +5. [Использование Bash скрипта](#использование-bash-скрипта) +6. [Использование Python скрипта](#использование-python-скрипта) +7. [Автоматизация обновления](#автоматизация-обновления) +8. [Устранение неполадок](#устранение-неполадок) --- -## Шаг 1. Покупка и получение SSL Wildcard сертификата для dfv24.com -1. На reg.ru или любом другом удостоверяющем центре (CA) закажите wildcard сертификат на домен вида `*.dfv24.com`. -2. Получите файлы сертификата: - - Главный сертификат (CRT) - - Промежуточные сертификаты (CA Bundle) - - Приватный ключ (KEY) +## Введение + +В проекте представлены два скрипта для автоматического создания и обновления SSL сертификатов Let's Encrypt с использованием DNS-валидации через API reg.ru: + +1. **letsencrypt_regru_dns.sh** - Bash скрипт с использованием плагина certbot-dns-regru +2. **letsencrypt_regru_api.py** - Python скрипт с прямым взаимодействием с API reg.ru + +Оба скрипта поддерживают: +- Создание wildcard сертификатов (*.domain.com) +- Автоматическое обновление сертификатов +- DNS-валидацию через API reg.ru +- Логирование всех операций +- Перезагрузку веб-сервера после обновления --- -## Шаг 2. Импорт вашего SSL сертификата в Nginx Proxy Manager -1. Авторизуйтесь в Nginx Proxy Manager на http://192.168.10.14:81/ -2. Перейдите в раздел **SSL Certificates** → кнопку **Add SSL Certificate** -3. Выберите **Custom** (пользовательский сертификат) -4. В поля вставьте: - - **Certificate** — основной CRT + CA Bundle (если CA Bundle раздельно, склейте в один файл или вставляйте последовательно) - - **Key** — содержимое приватного ключа - - Имя сертификата задайте, например, `dfv24_wildcard` -5. Сохраните +## Требования + +### Общие требования +- Операционная система: Linux (Ubuntu/Debian/CentOS) +- Права: root или sudo +- Домен зарегистрирован на reg.ru +- Доступ к API reg.ru (имя пользователя и пароль) + +### Для Bash скрипта +- certbot +- certbot-dns-regru (плагин) +- curl +- jq +- openssl + +### Для Python скрипта +- Python 3.6+ +- certbot +- pip3 +- Модули Python: requests, cryptography --- -## Шаг 3. Настройка прокси-хостов с использованием глобального сертификата +## Установка зависимостей -1. Перейдите в **Proxy Hosts** → **Add Proxy Host** -2. Заполните поля: - - **Domain Names**: Например, `sub1.dfv24.com` (для первого поддомена) - - **Scheme**: http или https, в зависимости от бекенда - - **Forward Hostname / IP**: IP или DNS адрес вашего внутреннего сервиса - - **Forward Port**: порт сервиса (например, 80 или 443) -3. Включите **SSL** → Отметьте **Use a shared SSL certificate** (если такая опция доступна) или выберите ранее импортированный сертификат из списка -4. Активируйте: **Block Common Exploits**, **Websockets Support**, выставьте Redirect HTTP to HTTPS, если требуется -5. Сохраните прокси-хост +### Ubuntu/Debian -6. Повторите для всех поддоменов, указывая нужные домены и выбирая тот же wildcard SSL сертификат +```bash +# Обновление пакетов +sudo apt-get update + +# Установка базовых зависимостей +sudo apt-get install -y certbot curl jq openssl python3 python3-pip + +# Для Python скрипта +sudo pip3 install certbot-dns-regru requests cryptography + +# Для Bash скрипта (если используете) +sudo pip3 install certbot-dns-regru +``` + +### CentOS/RHEL + +```bash +# Обновление пакетов +sudo yum update -y + +# Установка EPEL репозитория +sudo yum install -y epel-release + +# Установка базовых зависимостей +sudo yum install -y certbot curl jq openssl python3 python3-pip + +# Установка плагина +sudo pip3 install certbot-dns-regru requests cryptography +``` --- -## Шаг 4. Настройка DNS записей на reg.ru +## Настройка -1. Войдите в панель управления доменом на reg.ru -2. Создайте или отредактируйте DNS записи типа A: - - `dfv24.com` → IP вашего Nginx Proxy Manager (например, 192.168.10.14) - - `*.dfv24.com` → тот же IP или конкретные поддомены, если есть специальные -3. Сохраните изменения -4. Дождитесь обновления DNS (от нескольких минут до 24 часов) +### 1. Получение учетных данных API reg.ru + +1. Войдите в личный кабинет на сайте [reg.ru](https://www.reg.ru) +2. Перейдите в раздел "Управление API" +3. Используйте ваше имя пользователя и пароль для доступа к API +4. Убедитесь, что у вас есть права на управление DNS-записями + +### 2. Настройка конфигурации + +#### Для Python скрипта + +Создайте файл конфигурации на основе примера: + +```bash +# Создание примера конфигурации +sudo python3 letsencrypt_regru_api.py --create-config /etc/letsencrypt/regru_config.json + +# Редактирование конфигурации +sudo nano /etc/letsencrypt/regru_config.json +``` + +Отредактируйте параметры: + +```json +{ + "regru_username": "your_actual_username", + "regru_password": "your_actual_password", + "domain": "dfv24.com", + "wildcard": true, + "email": "admin@dfv24.com", + "cert_dir": "/etc/letsencrypt/live", + "log_file": "/var/log/letsencrypt_regru.log", + "dns_propagation_wait": 60, + "dns_check_attempts": 10, + "dns_check_interval": 10 +} +``` + +#### Для Bash скрипта + +Отредактируйте переменные в начале скрипта: + +```bash +sudo nano letsencrypt_regru_dns.sh +``` + +Измените следующие параметры: + +```bash +REGRU_USERNAME="your_actual_username" +REGRU_PASSWORD="your_actual_password" +DOMAIN="dfv24.com" +WILDCARD_DOMAIN="*.dfv24.com" +EMAIL="admin@dfv24.com" +``` + +### 3. Установка прав доступа + +```bash +# Для Bash скрипта +sudo chmod +x letsencrypt_regru_dns.sh + +# Для Python скрипта +sudo chmod +x letsencrypt_regru_api.py + +# Защита файла конфигурации +sudo chmod 600 /etc/letsencrypt/regru_config.json +``` --- -## Шаг 5. Тест и проверка работы +## Использование Bash скрипта -1. В браузере откройте любой из поддоменов `https://sub1.dfv24.com` -2. Сертификат должен быть валидным, выданным на wildcard `*.dfv24.com` -3. Проверьте работу прокси и корректность подстановки сертификата -4. При необходимости проверьте логи Nginx Proxy Manager и исправьте ошибки +### Первый запуск (получение сертификата) + +```bash +sudo ./letsencrypt_regru_dns.sh +``` + +Скрипт автоматически: +1. Проверит зависимости +2. Создаст файл с учетными данными +3. Установит плагин certbot-dns-regru (если не установлен) +4. Проверит наличие существующего сертификата +5. Получит новый сертификат или обновит существующий +6. Перезагрузит веб-сервер + +### Просмотр логов + +```bash +sudo tail -f /var/log/letsencrypt_regru.log +``` + +### Ручная проверка сертификата + +```bash +sudo certbot certificates +``` --- -## Дополнительно +## Использование Python скрипта -- Если в Nginx Proxy Manager нет GUI опции выбора общего сертификата, можно вручную сконфигурировать конфиги через директорий `/data/nginx/proxy_host` и прописать SSL сертификат для всех хостов. -- При обновлении сертификата — повторно импортировать его в Nginx Proxy Manager. -- Можно использовать LetsEncrypt для автоматического получения wildcard сертификата с помощью DNS-валидации (если поддерживается вашим DNS-провайдером). +### Создание примера конфигурации + +```bash +sudo python3 letsencrypt_regru_api.py --create-config /etc/letsencrypt/regru_config.json +``` + +### Получение нового сертификата + +```bash +# С использованием конфигурационного файла +sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --obtain + +# С подробным выводом +sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --obtain -v +``` + +### Обновление существующего сертификата + +```bash +sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --renew +``` + +### Проверка срока действия сертификата + +```bash +sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --check +``` + +### Автоматический режим (проверка и обновление) + +```bash +# Скрипт сам определит, нужно ли обновление +sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json +``` + +### Опции командной строки + +``` +-c, --config FILE Путь к файлу конфигурации (JSON) +--create-config FILE Создать пример файла конфигурации +--obtain Получить новый сертификат +--renew Обновить существующий сертификат +--check Проверить срок действия сертификата +-v, --verbose Подробный вывод +``` --- -# Итог +## Автоматизация обновления -Используйте один wildcard сертификат для всех поддоменов, импортируйте его как пользовательский сертификат в Nginx Proxy Manager, при создании прокси-хостов выбирайте его в настройках SSL. Управляйте DNS записями на reg.ru, направляя domен на IP Nginx Proxy Manager. -Это позволит юридически использовать единый сертификат для всех сервисов с различными поддоменами под вашим доменом dfv24.com. +Let's Encrypt сертификаты действительны 90 дней. Рекомендуется настроить автоматическое обновление. + +### Использование cron (для обоих скриптов) + +#### Для Python скрипта + +```bash +# Открыть crontab +sudo crontab -e + +# Добавить задачу (проверка каждый день в 3:00 AM) +0 3 * * * /usr/bin/python3 /path/to/letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json >> /var/log/letsencrypt_cron.log 2>&1 +``` + +#### Для Bash скрипта + +```bash +# Открыть crontab +sudo crontab -e + +# Добавить задачу (проверка каждый день в 3:00 AM) +0 3 * * * /path/to/letsencrypt_regru_dns.sh >> /var/log/letsencrypt_cron.log 2>&1 +``` + +### Использование systemd timer + +Создайте systemd service и timer для более гибкого управления: + +#### Создание service файла + +```bash +sudo nano /etc/systemd/system/letsencrypt-regru.service +``` + +Содержимое: + +```ini +[Unit] +Description=Let's Encrypt Certificate Renewal with reg.ru DNS +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/python3 /path/to/letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +``` + +#### Создание timer файла + +```bash +sudo nano /etc/systemd/system/letsencrypt-regru.timer +``` + +Содержимое: + +```ini +[Unit] +Description=Daily Let's Encrypt Certificate Check and Renewal +Requires=letsencrypt-regru.service + +[Timer] +OnCalendar=daily +Persistent=true +RandomizedDelaySec=1h + +[Install] +WantedBy=timers.target +``` + +#### Активация timer + +```bash +# Перезагрузка systemd +sudo systemctl daemon-reload + +# Включение и запуск timer +sudo systemctl enable letsencrypt-regru.timer +sudo systemctl start letsencrypt-regru.timer + +# Проверка статуса +sudo systemctl status letsencrypt-regru.timer +sudo systemctl list-timers +``` + +--- + +## Устранение неполадок + +### Проблема: Ошибка аутентификации API reg.ru + +**Решение:** +- Проверьте правильность имени пользователя и пароля +- Убедитесь, что у вашего аккаунта есть доступ к API +- Проверьте, что домен находится под управлением вашего аккаунта + +```bash +# Проверка доступа к API +curl -X POST "https://api.reg.ru/api/regru2/user/get_balance" \ + -d "username=YOUR_USERNAME" \ + -d "password=YOUR_PASSWORD" \ + -d "output_format=json" +``` + +### Проблема: DNS запись не распространяется + +**Решение:** +- Увеличьте параметр `dns_propagation_wait` в конфигурации (например, до 120 секунд) +- Проверьте DNS записи вручную: + +```bash +nslookup -type=TXT _acme-challenge.dfv24.com +# или +dig TXT _acme-challenge.dfv24.com +``` + +### Проблема: Certbot не установлен + +**Решение:** + +```bash +# Ubuntu/Debian +sudo apt-get install certbot + +# CentOS/RHEL +sudo yum install certbot + +# Или через snap +sudo snap install --classic certbot +``` + +### Проблема: Плагин certbot-dns-regru не найден + +**Решение:** + +```bash +# Установка через pip +sudo pip3 install certbot-dns-regru + +# Или через pip3 с обновлением +sudo pip3 install --upgrade certbot-dns-regru +``` + +### Проблема: Недостаточно прав + +**Решение:** +- Убедитесь, что запускаете скрипт от имени root или с sudo +- Проверьте права доступа к директории `/etc/letsencrypt/` + +```bash +sudo chmod 755 /etc/letsencrypt/ +sudo chown -R root:root /etc/letsencrypt/ +``` + +### Проблема: Ошибка при перезагрузке веб-сервера + +**Решение:** +- Проверьте, какой веб-сервер используется: + +```bash +systemctl status nginx +systemctl status apache2 +``` + +- Вручную перезагрузите веб-сервер: + +```bash +# Для Nginx +sudo systemctl reload nginx + +# Для Apache +sudo systemctl reload apache2 +``` + +### Просмотр подробных логов certbot + +```bash +# Логи certbot +sudo tail -f /var/log/letsencrypt/letsencrypt.log + +# Логи скрипта +sudo tail -f /var/log/letsencrypt_regru.log +``` + +### Тестовый режим (staging) + +Для тестирования используйте staging окружение Let's Encrypt: + +```bash +# Добавьте опцию --staging к команде certbot +certbot certonly --staging --dns-regru ... +``` + +--- + +## Проверка полученного сертификата + +### Просмотр информации о сертификате + +```bash +# Просмотр всех сертификатов +sudo certbot certificates + +# Просмотр конкретного сертификата +sudo openssl x509 -in /etc/letsencrypt/live/dfv24.com/cert.pem -text -noout + +# Проверка даты истечения +sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/dfv24.com/cert.pem +``` + +### Проверка сертификата в браузере + +1. Откройте ваш сайт в браузере: `https://dfv24.com` +2. Нажмите на иконку замка в адресной строке +3. Проверьте информацию о сертификате +4. Убедитесь, что сертификат выдан Let's Encrypt и покрывает wildcard домен + +### Онлайн проверка SSL + +- [SSL Labs](https://www.ssllabs.com/ssltest/) +- [SSL Shopper](https://www.sslshopper.com/ssl-checker.html) + +--- + +## Использование сертификата в Nginx Proxy Manager + +После получения сертификата вы можете использовать его в Nginx Proxy Manager: + +### Вариант 1: Импорт существующего сертификата + +1. Войдите в Nginx Proxy Manager: http://192.168.10.14:81/ +2. Перейдите в **SSL Certificates** → **Add SSL Certificate** +3. Выберите **Custom** +4. Вставьте содержимое файлов: + - Certificate Key: `/etc/letsencrypt/live/dfv24.com/privkey.pem` + - Certificate: `/etc/letsencrypt/live/dfv24.com/fullchain.pem` +5. Сохраните сертификат + +### Вариант 2: Прямая настройка Nginx + +Если вы используете Nginx напрямую, добавьте в конфигурацию: + +```nginx +server { + listen 443 ssl http2; + server_name dfv24.com *.dfv24.com; + + ssl_certificate /etc/letsencrypt/live/dfv24.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/dfv24.com/privkey.pem; + + # Дополнительные SSL настройки + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # ... остальная конфигурация +} +``` + +--- + +## Поддержка и вопросы + +При возникновении проблем: + +1. Проверьте логи: `/var/log/letsencrypt_regru.log` +2. Проверьте логи certbot: `/var/log/letsencrypt/letsencrypt.log` +3. Используйте подробный режим: `-v` для Python скрипта +4. Проверьте документацию reg.ru API: https://www.reg.ru/support/api +5. Проверьте документацию Let's Encrypt: https://letsencrypt.org/docs/ + +--- + +## Заключение + +Оба скрипта предоставляют надежное решение для автоматического управления SSL сертификатами Let's Encrypt с использованием DNS-валидации через API reg.ru. + +**Рекомендации:** +- Используйте Python скрипт для более гибкой настройки и интеграции +- Используйте Bash скрипт для простоты и минимальных зависимостей +- Настройте автоматическое обновление через cron или systemd timer +- Регулярно проверяйте логи и статус сертификатов +- Храните учетные данные в безопасности (chmod 600) + +Успешной автоматизации! 🔒 diff --git a/SSL_SCRIPTS_README.md b/SSL_SCRIPTS_README.md new file mode 100644 index 0000000..5627e77 --- /dev/null +++ b/SSL_SCRIPTS_README.md @@ -0,0 +1,170 @@ +# Автоматизация SSL сертификатов Let's Encrypt для reg.ru + +Набор скриптов для автоматического создания и обновления SSL сертификатов Let's Encrypt с использованием DNS-валидации через API reg.ru. + +## 📁 Содержимое проекта + +- **letsencrypt_regru_dns.sh** - Bash скрипт (Linux) с certbot-dns-regru +- **letsencrypt_regru_api.py** - Python скрипт с прямым API интеграцией +- **letsencrypt_regru.ps1** - PowerShell скрипт (Windows) +- **config.json.example** - Пример файла конфигурации +- **USAGE.md** - Подробная инструкция по использованию + +## 🚀 Быстрый старт + +### Linux (Bash) + +```bash +# 1. Отредактируйте конфигурацию в скрипте +nano letsencrypt_regru_dns.sh + +# 2. Установите права +chmod +x letsencrypt_regru_dns.sh + +# 3. Запустите +sudo ./letsencrypt_regru_dns.sh +``` + +### Linux (Python) + +```bash +# 1. Создайте конфигурацию +sudo python3 letsencrypt_regru_api.py --create-config /etc/letsencrypt/regru_config.json + +# 2. Отредактируйте конфигурацию +sudo nano /etc/letsencrypt/regru_config.json + +# 3. Получите сертификат +sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --obtain +``` + +### Windows (PowerShell) + +```powershell +# 1. Создайте файл конфигурации config.json на основе config.json.example + +# 2. Запустите скрипт +.\letsencrypt_regru.ps1 -ConfigFile .\config.json +``` + +## ⚙️ Конфигурация + +Отредактируйте `config.json`: + +```json +{ + "regru_username": "ваш_логин", + "regru_password": "ваш_пароль", + "domain": "dfv24.com", + "wildcard": true, + "email": "admin@dfv24.com" +} +``` + +## 📋 Требования + +### Linux +- certbot +- Python 3.6+ +- pip3 +- requests, cryptography (Python модули) +- certbot-dns-regru (опционально) + +### Windows +- certbot +- PowerShell 5.1+ +- openssl (для проверки сертификатов) + +## 🔄 Автоматическое обновление + +### Через cron (Linux) + +```bash +# Добавьте в crontab +sudo crontab -e + +# Проверка каждый день в 3:00 +0 3 * * * /usr/bin/python3 /path/to/letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json +``` + +### Через Task Scheduler (Windows) + +1. Откройте Task Scheduler +2. Создайте новую задачу +3. Триггер: Ежедневно в 3:00 +4. Действие: Запуск PowerShell скрипта + +## 📖 Функции + +✅ Создание wildcard сертификатов (*.domain.com) +✅ Автоматическая DNS-валидация через API reg.ru +✅ Проверка срока действия сертификата +✅ Автоматическое обновление перед истечением +✅ Перезагрузка веб-сервера после обновления +✅ Подробное логирование всех операций + +## 🔧 Использование с Nginx Proxy Manager + +После получения сертификата: + +1. Войдите в NPM: http://192.168.10.14:81/ +2. SSL Certificates → Add SSL Certificate → Custom +3. Вставьте содержимое: + - Certificate Key: `/etc/letsencrypt/live/domain.com/privkey.pem` + - Certificate: `/etc/letsencrypt/live/domain.com/fullchain.pem` + +## 📝 Логи + +- Bash: `/var/log/letsencrypt_regru.log` +- Python: `/var/log/letsencrypt_regru.log` +- PowerShell: `.\letsencrypt_regru.log` +- Certbot: `/var/log/letsencrypt/letsencrypt.log` + +## 🆘 Устранение неполадок + +### Ошибка аутентификации API +- Проверьте учетные данные reg.ru +- Убедитесь, что домен под вашим управлением + +### DNS запись не распространяется +- Увеличьте `dns_propagation_wait` до 120 секунд +- Проверьте DNS: `nslookup -type=TXT _acme-challenge.domain.com` + +### Certbot не найден +```bash +# Ubuntu/Debian +sudo apt-get install certbot + +# Или через snap +sudo snap install --classic certbot +``` + +## 📚 Документация + +Подробная документация в файле [USAGE.md](USAGE.md) + +## 🔐 Безопасность + +- Храните учетные данные в безопасности +- Используйте `chmod 600` для конфигурационных файлов +- Регулярно обновляйте пароли + +## ⚠️ Важно + +- Let's Encrypt сертификаты действительны 90 дней +- Рекомендуется настроить автоматическое обновление +- Для wildcard сертификатов требуется DNS-валидация + +## 📞 Поддержка + +- [Документация reg.ru API](https://www.reg.ru/support/api) +- [Документация Let's Encrypt](https://letsencrypt.org/docs/) +- [Certbot Documentation](https://certbot.eff.org/docs/) + +## 📄 Лицензия + +Скрипты предоставляются "как есть" для свободного использования. + +--- + +**Успешной автоматизации! 🔒** diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..aa2f8cc --- /dev/null +++ b/config.json.example @@ -0,0 +1,12 @@ +{ + "regru_username": "your_username", + "regru_password": "your_password", + "domain": "dfv24.com", + "wildcard": true, + "email": "admin@dfv24.com", + "cert_dir": "/etc/letsencrypt/live", + "log_file": "/var/log/letsencrypt_regru.log", + "dns_propagation_wait": 60, + "dns_check_attempts": 10, + "dns_check_interval": 10 +} diff --git a/letsencrypt_regru.ps1 b/letsencrypt_regru.ps1 new file mode 100644 index 0000000..1535c43 --- /dev/null +++ b/letsencrypt_regru.ps1 @@ -0,0 +1,556 @@ +# ============================================================================ +# Скрипт для создания и обновления SSL сертификата Let's Encrypt +# с использованием DNS-валидации через API reg.ru (PowerShell версия) +# +# Автор: GitHub Copilot +# Дата: 27.10.2025 +# Описание: PowerShell версия скрипта для Windows окружения +# ============================================================================ + +<# +.SYNOPSIS + Автоматизация получения SSL сертификатов Let's Encrypt через DNS-валидацию reg.ru + +.DESCRIPTION + Скрипт позволяет автоматически получать и обновлять SSL сертификаты Let's Encrypt + для доменов на reg.ru используя DNS-01 challenge через API + +.PARAMETER Domain + Основной домен (например, dfv24.com) + +.PARAMETER Email + Email для уведомлений Let's Encrypt + +.PARAMETER RegRuUsername + Имя пользователя reg.ru + +.PARAMETER RegRuPassword + Пароль reg.ru + +.PARAMETER Wildcard + Создать wildcard сертификат (*.domain.com) + +.PARAMETER ConfigFile + Путь к файлу конфигурации JSON + +.EXAMPLE + .\letsencrypt_regru.ps1 -Domain "dfv24.com" -Email "admin@dfv24.com" -Wildcard +#> + +param( + [Parameter(Mandatory=$false)] + [string]$Domain = "dfv24.com", + + [Parameter(Mandatory=$false)] + [string]$Email = "admin@dfv24.com", + + [Parameter(Mandatory=$false)] + [string]$RegRuUsername = "", + + [Parameter(Mandatory=$false)] + [string]$RegRuPassword = "", + + [Parameter(Mandatory=$false)] + [switch]$Wildcard = $true, + + [Parameter(Mandatory=$false)] + [string]$ConfigFile = ".\config.json", + + [Parameter(Mandatory=$false)] + [switch]$Verbose = $false +) + +# ============================================================================ +# КОНФИГУРАЦИЯ +# ============================================================================ + +$Script:Config = @{ + RegRuApiUrl = "https://api.reg.ru/api/regru2" + CertbotPath = "certbot" + LogFile = ".\letsencrypt_regru.log" + DnsPropagationWait = 60 + DnsCheckAttempts = 10 + DnsCheckInterval = 10 +} + +# ============================================================================ +# ФУНКЦИИ ЛОГИРОВАНИЯ +# ============================================================================ + +function Write-Log { + param( + [string]$Message, + [string]$Level = "INFO" + ) + + $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $LogMessage = "[$Timestamp] [$Level] $Message" + + # Вывод в консоль + switch ($Level) { + "ERROR" { Write-Host $LogMessage -ForegroundColor Red } + "WARNING" { Write-Host $LogMessage -ForegroundColor Yellow } + "SUCCESS" { Write-Host $LogMessage -ForegroundColor Green } + default { Write-Host $LogMessage } + } + + # Запись в файл + Add-Content -Path $Script:Config.LogFile -Value $LogMessage +} + +# ============================================================================ +# ФУНКЦИИ РАБОТЫ С КОНФИГУРАЦИЕЙ +# ============================================================================ + +function Load-Configuration { + param([string]$ConfigPath) + + Write-Log "Загрузка конфигурации из: $ConfigPath" + + if (Test-Path $ConfigPath) { + try { + $config = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json + + # Обновляем параметры скрипта из конфигурации + if ($config.domain) { $Script:Domain = $config.domain } + if ($config.email) { $Script:Email = $config.email } + if ($config.regru_username) { $Script:RegRuUsername = $config.regru_username } + if ($config.regru_password) { $Script:RegRuPassword = $config.regru_password } + if ($null -ne $config.wildcard) { $Script:Wildcard = $config.wildcard } + + Write-Log "Конфигурация успешно загружена" "SUCCESS" + return $true + } + catch { + Write-Log "Ошибка при загрузке конфигурации: $_" "ERROR" + return $false + } + } + else { + Write-Log "Файл конфигурации не найден: $ConfigPath" "WARNING" + return $false + } +} + +function Create-SampleConfig { + param([string]$OutputPath = ".\config.json") + + $sampleConfig = @{ + regru_username = "your_username" + regru_password = "your_password" + domain = "dfv24.com" + wildcard = $true + email = "admin@dfv24.com" + dns_propagation_wait = 60 + dns_check_attempts = 10 + dns_check_interval = 10 + } + + $sampleConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath + Write-Log "Пример конфигурации создан: $OutputPath" "SUCCESS" +} + +# ============================================================================ +# ФУНКЦИИ РАБОТЫ С REG.RU API +# ============================================================================ + +function Invoke-RegRuApi { + param( + [string]$Method, + [hashtable]$Params + ) + + $url = "$($Script:Config.RegRuApiUrl)/$Method" + + # Добавляем учетные данные + $Params["username"] = $Script:RegRuUsername + $Params["password"] = $Script:RegRuPassword + $Params["output_format"] = "json" + + Write-Log "Отправка запроса к API: $Method" "DEBUG" + + try { + $response = Invoke-RestMethod -Uri $url -Method Post -Body $Params -ContentType "application/x-www-form-urlencoded" + + if ($response.result -eq "success") { + Write-Log "Запрос $Method выполнен успешно" "DEBUG" + return $response + } + else { + $errorMsg = if ($response.error_text) { $response.error_text } else { "Неизвестная ошибка" } + Write-Log "Ошибка API: $errorMsg" "ERROR" + throw "API Error: $errorMsg" + } + } + catch { + Write-Log "Ошибка HTTP запроса: $_" "ERROR" + throw + } +} + +function Get-DnsRecords { + param([string]$Domain) + + Write-Log "Получение DNS записей для домена: $Domain" + + $params = @{ + domain = $Domain + } + + $response = Invoke-RegRuApi -Method "zone/get_resource_records" -Params $params + + if ($response.answer -and $response.answer.records) { + $records = $response.answer.records + Write-Log "Получено $($records.Count) DNS записей" + return $records + } + else { + Write-Log "DNS записи не найдены" "WARNING" + return @() + } +} + +function Add-TxtRecord { + param( + [string]$Domain, + [string]$Subdomain, + [string]$TxtValue + ) + + Write-Log "Добавление TXT записи: $Subdomain.$Domain = $TxtValue" + + $params = @{ + domain = $Domain + subdomain = $Subdomain + text = $TxtValue + output_content_type = "plain" + } + + try { + $null = Invoke-RegRuApi -Method "zone/add_txt" -Params $params + Write-Log "TXT запись успешно добавлена" "SUCCESS" + return $true + } + catch { + Write-Log "Не удалось добавить TXT запись: $_" "ERROR" + return $false + } +} + +function Remove-TxtRecord { + param( + [string]$Domain, + [string]$Subdomain, + [string]$TxtValue + ) + + Write-Log "Удаление TXT записи: $Subdomain.$Domain" + + # Получаем список записей + $records = Get-DnsRecords -Domain $Domain + + # Ищем нужную TXT запись + $record = $records | Where-Object { + $_.rectype -eq "TXT" -and + $_.subdomain -eq $Subdomain -and + $_.text -eq $TxtValue + } | Select-Object -First 1 + + if (-not $record) { + Write-Log "TXT запись для удаления не найдена" "WARNING" + return $false + } + + $params = @{ + domain = $Domain + record_id = $record.id + } + + try { + $null = Invoke-RegRuApi -Method "zone/remove_record" -Params $params + Write-Log "TXT запись успешно удалена" "SUCCESS" + return $true + } + catch { + Write-Log "Не удалось удалить TXT запись: $_" "ERROR" + return $false + } +} + +# ============================================================================ +# ФУНКЦИИ РАБОТЫ С CERTBOT +# ============================================================================ + +function Test-CertbotInstalled { + try { + $version = & certbot --version 2>&1 + Write-Log "Certbot установлен: $version" + return $true + } + catch { + Write-Log "Certbot не установлен!" "ERROR" + Write-Log "Установите Certbot: https://certbot.eff.org/instructions" "ERROR" + return $false + } +} + +function Get-CertificateExpiry { + param([string]$Domain) + + $certPath = Join-Path $env:ProgramData "letsencrypt\live\$Domain\cert.pem" + + if (-not (Test-Path $certPath)) { + Write-Log "Сертификат не найден: $certPath" + return $null + } + + try { + # Используем openssl для проверки сертификата + $expiryText = & openssl x509 -enddate -noout -in $certPath 2>&1 + + if ($expiryText -match "notAfter=(.+)") { + $expiryDate = [DateTime]::Parse($matches[1]) + $daysLeft = ($expiryDate - (Get-Date)).Days + + Write-Log "Сертификат истекает: $($expiryDate.ToString('yyyy-MM-dd'))" + Write-Log "Осталось дней: $daysLeft" + + return $daysLeft + } + } + catch { + Write-Log "Ошибка при проверке сертификата: $_" "ERROR" + } + + return $null +} + +function Invoke-DnsChallenge { + param( + [string]$Domain, + [string]$ValidationToken + ) + + Write-Log "=== DNS Challenge: Добавление TXT записи ===" "INFO" + + # Извлекаем поддомен + $subdomain = "_acme-challenge" + + # Добавляем TXT запись + $success = Add-TxtRecord -Domain $Script:Domain -Subdomain $subdomain -TxtValue $ValidationToken + + if ($success) { + # Ждем распространения DNS + $waitTime = $Script:Config.DnsPropagationWait + Write-Log "Ожидание распространения DNS ($waitTime секунд)..." + Start-Sleep -Seconds $waitTime + + Write-Log "DNS валидация готова" "SUCCESS" + return $true + } + + return $false +} + +function Invoke-DnsCleanup { + param( + [string]$Domain, + [string]$ValidationToken + ) + + Write-Log "=== DNS Challenge: Удаление TXT записи ===" "INFO" + + $subdomain = "_acme-challenge" + + return Remove-TxtRecord -Domain $Script:Domain -Subdomain $subdomain -TxtValue $ValidationToken +} + +# ============================================================================ +# ГЛАВНЫЕ ФУНКЦИИ +# ============================================================================ + +function Get-Certificate { + param([string]$Domain, [bool]$WildcardCert) + + Write-Log "=== Запрос нового SSL сертификата ===" "INFO" + + # Формируем список доменов + $domains = @($Domain) + if ($WildcardCert) { + $domains += "*.$Domain" + } + + $domainArgs = @() + foreach ($d in $domains) { + $domainArgs += "-d" + $domainArgs += $d + } + + Write-Log "Домены для сертификата: $($domains -join ', ')" + + # Создаем временные скрипты для хуков + $authHookScript = Join-Path $env:TEMP "certbot_auth_hook.ps1" + $cleanupHookScript = Join-Path $env:TEMP "certbot_cleanup_hook.ps1" + + # Скрипт для аутентификации + @" +param([string]`$Domain, [string]`$Token) +# Вызов функции добавления TXT записи +# Здесь должна быть логика работы с API reg.ru +"@ | Set-Content -Path $authHookScript + + # Скрипт для очистки + @" +param([string]`$Domain, [string]`$Token) +# Вызов функции удаления TXT записи +"@ | Set-Content -Path $cleanupHookScript + + Write-Log "Примечание: Для полной автоматизации используйте Linux версию скрипта" "WARNING" + Write-Log "На Windows рекомендуется использовать плагин certbot-dns-регru или выполнить вручную" "WARNING" + + # Команда certbot (базовая, требует ручной DNS валидации) + $certbotArgs = @( + "certonly", + "--manual", + "--preferred-challenges", "dns", + "--email", $Script:Email, + "--agree-tos", + "--manual-public-ip-logging-ok" + ) + $domainArgs + + Write-Log "Запуск certbot..." + Write-Log "ВАЖНО: Certbot запросит вас добавить TXT записи в DNS" "WARNING" + Write-Log "Используйте API reg.ru или добавьте записи вручную через панель управления" "WARNING" + + try { + & certbot @certbotArgs + + Write-Log "Процесс получения сертификата завершен" "SUCCESS" + return $true + } + catch { + Write-Log "Ошибка при получении сертификата: $_" "ERROR" + return $false + } +} + +function Update-Certificate { + Write-Log "=== Обновление SSL сертификата ===" "INFO" + + try { + & certbot renew + + Write-Log "Проверка обновления завершена" "SUCCESS" + return $true + } + catch { + Write-Log "Ошибка при обновлении: $_" "ERROR" + return $false + } +} + +function Show-CertificateInfo { + param([string]$Domain) + + $certPath = Join-Path $env:ProgramData "letsencrypt\live\$Domain\cert.pem" + + if (-not (Test-Path $certPath)) { + Write-Log "Сертификат не найден" "WARNING" + return + } + + Write-Log ("=" * 60) + Write-Log "ИНФОРМАЦИЯ О СЕРТИФИКАТЕ" + Write-Log ("=" * 60) + + try { + $certInfo = & openssl x509 -in $certPath -text -noout + + # Выводим основную информацию + $certInfo -split "`n" | Where-Object { + $_ -match "Subject:|Issuer:|Not Before|Not After|DNS:" + } | ForEach-Object { + Write-Log $_.Trim() + } + + Write-Log ("=" * 60) + Write-Log "ПУТИ К ФАЙЛАМ СЕРТИФИКАТА:" + Write-Log " Сертификат: $certPath" + Write-Log " Приватный ключ: $(Join-Path $env:ProgramData "letsencrypt\live\$Domain\privkey.pem")" + Write-Log " Цепочка: $(Join-Path $env:ProgramData "letsencrypt\live\$Domain\chain.pem")" + Write-Log " Полная цепочка: $(Join-Path $env:ProgramData "letsencrypt\live\$Domain\fullchain.pem")" + Write-Log ("=" * 60) + } + catch { + Write-Log "Ошибка при чтении сертификата: $_" "ERROR" + } +} + +# ============================================================================ +# ОСНОВНАЯ ЛОГИКА +# ============================================================================ + +function Main { + Write-Log ("=" * 60) + Write-Log "СКРИПТ УПРАВЛЕНИЯ SSL СЕРТИФИКАТАМИ LET'S ENCRYPT" + Write-Log ("=" * 60) + + # Проверка прав администратора + $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) + $isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + + if (-not $isAdmin) { + Write-Log "ПРЕДУПРЕЖДЕНИЕ: Скрипт запущен без прав администратора" "WARNING" + Write-Log "Некоторые операции могут потребовать повышенных прав" "WARNING" + } + + # Загрузка конфигурации + if ($ConfigFile -and (Test-Path $ConfigFile)) { + Load-Configuration -ConfigPath $ConfigFile + } + + # Проверка обязательных параметров + if (-not $Script:RegRuUsername -or -not $Script:RegRuPassword) { + Write-Log "ОШИБКА: Не указаны учетные данные reg.ru" "ERROR" + Write-Log "Укажите RegRuUsername и RegRuPassword или создайте файл конфигурации" "ERROR" + return + } + + # Проверка Certbot + if (-not (Test-CertbotInstalled)) { + Write-Log "Установите Certbot и повторите попытку" "ERROR" + return + } + + # Проверка срока действия сертификата + $daysLeft = Get-CertificateExpiry -Domain $Script:Domain + + if ($null -eq $daysLeft) { + Write-Log "Сертификат не найден. Требуется создание нового." "INFO" + $success = Get-Certificate -Domain $Script:Domain -WildcardCert $Script:Wildcard + } + elseif ($daysLeft -lt 30) { + Write-Log "Сертификат истекает через $daysLeft дней. Требуется обновление!" "WARNING" + $success = Update-Certificate + } + else { + Write-Log "Сертификат действителен ($daysLeft дней)" "SUCCESS" + $success = $true + } + + if ($success) { + Show-CertificateInfo -Domain $Script:Domain + } + + Write-Log ("=" * 60) + Write-Log "Скрипт завершен" + Write-Log ("=" * 60) +} + +# ============================================================================ +# ТОЧКА ВХОДА +# ============================================================================ + +# Запуск основной функции +Main diff --git a/letsencrypt_regru_api.py b/letsencrypt_regru_api.py new file mode 100644 index 0000000..488ba1f --- /dev/null +++ b/letsencrypt_regru_api.py @@ -0,0 +1,831 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Скрипт для создания и обновления SSL сертификата Let's Encrypt +с использованием DNS-валидации через API reg.ru + +Автор: GitHub Copilot +Дата: 27.10.2025 + +Описание: + Этот скрипт автоматизирует процесс получения и обновления SSL сертификатов + Let's Encrypt для доменов, зарегистрированных на reg.ru, используя DNS-01 challenge. + Скрипт напрямую работает с API reg.ru для управления DNS записями. + +Требования: + - Python 3.6+ + - requests + - certbot + - cryptography + +Установка зависимостей: + pip install requests certbot cryptography +""" + +import os +import sys +import json +import time +import logging +import argparse +import subprocess +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Tuple + +try: + import requests +except ImportError: + print("ОШИБКА: Необходимо установить модуль 'requests'") + print("Выполните: pip install requests") + sys.exit(1) + +# ============================================================================== +# КОНФИГУРАЦИЯ +# ============================================================================== + +# Настройки по умолчанию +DEFAULT_CONFIG = { + # Учетные данные API reg.ru + "regru_username": "your_username", + "regru_password": "your_password", + + # Параметры домена + "domain": "dfv24.com", + "wildcard": True, # Создавать wildcard сертификат (*.domain.com) + + # Email для уведомлений Let's Encrypt + "email": "admin@dfv24.com", + + # Директории + "cert_dir": "/etc/letsencrypt/live", + "log_file": "/var/log/letsencrypt_regru.log", + + # Параметры DNS + "dns_propagation_wait": 60, # Время ожидания распространения DNS (секунды) + "dns_check_attempts": 10, # Количество попыток проверки DNS + "dns_check_interval": 10, # Интервал между проверками DNS (секунды) +} + +# API endpoints для reg.ru +REGRU_API_URL = "https://api.reg.ru/api/regru2" + +# ============================================================================== +# НАСТРОЙКА ЛОГИРОВАНИЯ +# ============================================================================== + +def setup_logging(log_file: str, verbose: bool = False) -> logging.Logger: + """ + Настройка системы логирования + + Args: + log_file: Путь к файлу лога + verbose: Режим подробного вывода + + Returns: + Logger объект + """ + log_level = logging.DEBUG if verbose else logging.INFO + + # Создаем директорию для логов, если не существует + log_dir = os.path.dirname(log_file) + if log_dir and not os.path.exists(log_dir): + os.makedirs(log_dir, exist_ok=True) + + # Настройка форматирования + formatter = logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + # Создаем logger + logger = logging.getLogger('LetsEncrypt_RegRU') + logger.setLevel(log_level) + + # Обработчик для файла + file_handler = logging.FileHandler(log_file, encoding='utf-8') + file_handler.setLevel(log_level) + file_handler.setFormatter(formatter) + + # Обработчик для консоли + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(log_level) + console_handler.setFormatter(formatter) + + # Добавляем обработчики + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger + + +# ============================================================================== +# КЛАСС ДЛЯ РАБОТЫ С API REG.RU +# ============================================================================== + +class RegRuAPI: + """Класс для работы с API reg.ru""" + + def __init__(self, username: str, password: str, logger: logging.Logger): + """ + Инициализация API клиента + + Args: + username: Имя пользователя reg.ru + password: Пароль reg.ru + logger: Logger объект + """ + self.username = username + self.password = password + self.logger = logger + self.session = requests.Session() + + def _make_request(self, method: str, params: Dict) -> Dict: + """ + Выполнение запроса к API reg.ru + + Args: + method: Название метода API + params: Параметры запроса + + Returns: + Ответ API в формате dict + """ + url = f"{REGRU_API_URL}/{method}" + + # Добавляем учетные данные к параметрам + params.update({ + "username": self.username, + "password": self.password, + "output_format": "json" + }) + + try: + self.logger.debug(f"Отправка запроса к API: {method}") + response = self.session.post(url, data=params) + response.raise_for_status() + + result = response.json() + + if result.get("result") == "success": + self.logger.debug(f"Запрос {method} выполнен успешно") + return result + else: + error_msg = result.get("error_text", "Неизвестная ошибка") + self.logger.error(f"Ошибка API: {error_msg}") + raise Exception(f"API Error: {error_msg}") + + except requests.exceptions.RequestException as e: + self.logger.error(f"Ошибка HTTP запроса: {e}") + raise + + def get_zone_records(self, domain: str) -> List[Dict]: + """ + Получение DNS записей домена + + Args: + domain: Доменное имя + + Returns: + Список DNS записей + """ + self.logger.info(f"Получение DNS записей для домена: {domain}") + + params = { + "domain": domain, + } + + result = self._make_request("zone/get_resource_records", params) + + if "answer" in result and "records" in result["answer"]: + records = result["answer"]["records"] + self.logger.info(f"Получено {len(records)} DNS записей") + return records + else: + self.logger.warning("DNS записи не найдены") + return [] + + def add_txt_record(self, domain: str, subdomain: str, txt_value: str) -> bool: + """ + Добавление TXT записи для DNS валидации + + Args: + domain: Основной домен + subdomain: Поддомен (например, _acme-challenge) + txt_value: Значение TXT записи + + Returns: + True если успешно, False в противном случае + """ + self.logger.info(f"Добавление TXT записи: {subdomain}.{domain} = {txt_value}") + + params = { + "domain": domain, + "subdomain": subdomain, + "text": txt_value, + "output_content_type": "plain" + } + + try: + self._make_request("zone/add_txt", params) + self.logger.info("TXT запись успешно добавлена") + return True + except Exception as e: + self.logger.error(f"Не удалось добавить TXT запись: {e}") + return False + + def remove_txt_record(self, domain: str, subdomain: str, txt_value: str) -> bool: + """ + Удаление TXT записи + + Args: + domain: Основной домен + subdomain: Поддомен + txt_value: Значение TXT записи + + Returns: + True если успешно, False в противном случае + """ + self.logger.info(f"Удаление TXT записи: {subdomain}.{domain}") + + # Сначала получаем список всех записей + records = self.get_zone_records(domain) + + # Ищем нужную TXT запись + record_id = None + for record in records: + if (record.get("rectype") == "TXT" and + record.get("subdomain") == subdomain and + record.get("text") == txt_value): + record_id = record.get("id") + break + + if not record_id: + self.logger.warning("TXT запись для удаления не найдена") + return False + + params = { + "domain": domain, + "record_id": record_id + } + + try: + self._make_request("zone/remove_record", params) + self.logger.info("TXT запись успешно удалена") + return True + except Exception as e: + self.logger.error(f"Не удалось удалить TXT запись: {e}") + return False + + +# ============================================================================== +# КЛАСС ДЛЯ РАБОТЫ С CERTBOT +# ============================================================================== + +class LetsEncryptManager: + """Класс для управления сертификатами Let's Encrypt""" + + def __init__(self, config: Dict, api: RegRuAPI, logger: logging.Logger): + """ + Инициализация менеджера сертификатов + + Args: + config: Конфигурация + api: API клиент reg.ru + logger: Logger объект + """ + self.config = config + self.api = api + self.logger = logger + self.domain = config["domain"] + self.email = config["email"] + self.cert_dir = os.path.join(config["cert_dir"], self.domain) + + def check_certbot_installed(self) -> bool: + """ + Проверка установки certbot + + Returns: + True если certbot установлен + """ + try: + result = subprocess.run( + ["certbot", "--version"], + capture_output=True, + text=True, + check=True + ) + self.logger.debug(f"Certbot установлен: {result.stdout.strip()}") + return True + except (subprocess.CalledProcessError, FileNotFoundError): + self.logger.error("Certbot не установлен!") + return False + + def check_certificate_expiry(self) -> Optional[int]: + """ + Проверка срока действия сертификата + + Returns: + Количество дней до истечения или None если сертификат не найден + """ + cert_file = os.path.join(self.cert_dir, "cert.pem") + + if not os.path.exists(cert_file): + self.logger.info("Сертификат не найден") + return None + + try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + + with open(cert_file, "rb") as f: + cert_data = f.read() + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) + + expiry_date = cert.not_valid_after + days_left = (expiry_date - datetime.now()).days + + self.logger.info(f"Сертификат истекает: {expiry_date.strftime('%Y-%m-%d')}") + self.logger.info(f"Осталось дней: {days_left}") + + return days_left + + except Exception as e: + self.logger.error(f"Ошибка при проверке сертификата: {e}") + return None + + def dns_challenge_hook(self, validation_domain: str, validation_token: str) -> bool: + """ + Обработчик DNS challenge - добавление TXT записи + + Args: + validation_domain: Домен для валидации (_acme-challenge.domain.com) + validation_token: Токен валидации + + Returns: + True если успешно + """ + self.logger.info("=== DNS Challenge: Добавление TXT записи ===") + + # Извлекаем поддомен из validation_domain + # Формат: _acme-challenge.domain.com или _acme-challenge + parts = validation_domain.replace(f".{self.domain}", "").split(".") + subdomain = parts[0] if parts else "_acme-challenge" + + # Добавляем TXT запись + success = self.api.add_txt_record(self.domain, subdomain, validation_token) + + if success: + # Ждем распространения DNS + wait_time = self.config.get("dns_propagation_wait", 60) + self.logger.info(f"Ожидание распространения DNS ({wait_time} секунд)...") + time.sleep(wait_time) + + # Проверяем DNS запись + if self.verify_dns_record(subdomain, validation_token): + self.logger.info("DNS валидация готова") + return True + else: + self.logger.warning("DNS запись не распространилась вовремя, но продолжаем...") + return True + + return False + + def dns_cleanup_hook(self, validation_domain: str, validation_token: str) -> bool: + """ + Обработчик очистки DNS challenge - удаление TXT записи + + Args: + validation_domain: Домен валидации + validation_token: Токен валидации + + Returns: + True если успешно + """ + self.logger.info("=== DNS Challenge: Удаление TXT записи ===") + + parts = validation_domain.replace(f".{self.domain}", "").split(".") + subdomain = parts[0] if parts else "_acme-challenge" + + return self.api.remove_txt_record(self.domain, subdomain, validation_token) + + def verify_dns_record(self, subdomain: str, expected_value: str) -> bool: + """ + Проверка наличия DNS записи + + Args: + subdomain: Поддомен + expected_value: Ожидаемое значение TXT записи + + Returns: + True если запись найдена + """ + import socket + + full_domain = f"{subdomain}.{self.domain}" + attempts = self.config.get("dns_check_attempts", 10) + interval = self.config.get("dns_check_interval", 10) + + self.logger.info(f"Проверка DNS записи для {full_domain}") + + for attempt in range(attempts): + try: + # Используем nslookup или dig через subprocess + result = subprocess.run( + ["nslookup", "-type=TXT", full_domain], + capture_output=True, + text=True, + timeout=10 + ) + + if expected_value in result.stdout: + self.logger.info(f"DNS запись найдена (попытка {attempt + 1})") + return True + + except Exception as e: + self.logger.debug(f"Попытка {attempt + 1}: DNS запись не найдена - {e}") + + if attempt < attempts - 1: + time.sleep(interval) + + self.logger.warning("DNS запись не найдена после всех попыток") + return False + + def obtain_certificate(self) -> bool: + """ + Получение нового сертификата + + Returns: + True если успешно + """ + self.logger.info("=== Запрос нового SSL сертификата ===") + + # Формируем список доменов + domains = [self.domain] + if self.config.get("wildcard", False): + domains.append(f"*.{self.domain}") + + domain_args = [] + for d in domains: + domain_args.extend(["-d", d]) + + # Команда certbot + cmd = [ + "certbot", "certonly", + "--manual", + "--preferred-challenges", "dns", + "--manual-auth-hook", f"{sys.executable} {os.path.abspath(__file__)} --auth-hook", + "--manual-cleanup-hook", f"{sys.executable} {os.path.abspath(__file__)} --cleanup-hook", + "--email", self.email, + "--agree-tos", + "--non-interactive", + "--expand", + ] + domain_args + + self.logger.info(f"Выполнение команды: {' '.join(cmd)}") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + + self.logger.info("Сертификат успешно получен!") + self.logger.debug(result.stdout) + return True + + except subprocess.CalledProcessError as e: + self.logger.error(f"Ошибка при получении сертификата: {e}") + self.logger.error(e.stderr) + return False + + def renew_certificate(self) -> bool: + """ + Обновление существующего сертификата + + Returns: + True если успешно + """ + self.logger.info("=== Обновление SSL сертификата ===") + + cmd = [ + "certbot", "renew", + "--manual", + "--manual-auth-hook", f"{sys.executable} {os.path.abspath(__file__)} --auth-hook", + "--manual-cleanup-hook", f"{sys.executable} {os.path.abspath(__file__)} --cleanup-hook", + "--non-interactive", + ] + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + + self.logger.info("Проверка обновления завершена") + self.logger.debug(result.stdout) + return True + + except subprocess.CalledProcessError as e: + self.logger.error(f"Ошибка при обновлении: {e}") + self.logger.error(e.stderr) + return False + + def display_certificate_info(self): + """Вывод информации о сертификате""" + cert_file = os.path.join(self.cert_dir, "cert.pem") + + if not os.path.exists(cert_file): + self.logger.warning("Сертификат не найден") + return + + self.logger.info("=" * 60) + self.logger.info("ИНФОРМАЦИЯ О СЕРТИФИКАТЕ") + self.logger.info("=" * 60) + + try: + result = subprocess.run( + ["openssl", "x509", "-in", cert_file, "-text", "-noout"], + capture_output=True, + text=True, + check=True + ) + + # Выводим только основную информацию + for line in result.stdout.split("\n"): + if any(keyword in line for keyword in ["Subject:", "Issuer:", "Not Before", "Not After", "DNS:"]): + self.logger.info(line.strip()) + + self.logger.info("=" * 60) + self.logger.info("ПУТИ К ФАЙЛАМ СЕРТИФИКАТА:") + self.logger.info(f" Сертификат: {self.cert_dir}/cert.pem") + self.logger.info(f" Приватный ключ: {self.cert_dir}/privkey.pem") + self.logger.info(f" Цепочка: {self.cert_dir}/chain.pem") + self.logger.info(f" Полная цепочка: {self.cert_dir}/fullchain.pem") + self.logger.info("=" * 60) + + except Exception as e: + self.logger.error(f"Ошибка при чтении сертификата: {e}") + + +# ============================================================================== +# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ +# ============================================================================== + +def reload_webserver(logger: logging.Logger): + """ + Перезагрузка веб-сервера + + Args: + logger: Logger объект + """ + logger.info("Перезагрузка веб-сервера...") + + # Проверяем какие сервисы активны + services = ["nginx", "apache2", "httpd"] + + for service in services: + try: + # Проверяем статус + result = subprocess.run( + ["systemctl", "is-active", service], + capture_output=True, + text=True + ) + + if result.stdout.strip() == "active": + # Перезагружаем + subprocess.run( + ["systemctl", "reload", service], + check=True + ) + logger.info(f"Сервис {service} перезагружен") + return + + except Exception as e: + logger.debug(f"Сервис {service} не активен или ошибка: {e}") + + logger.warning("Активный веб-сервер не найден") + + +def load_config(config_file: Optional[str] = None) -> Dict: + """ + Загрузка конфигурации из файла или использование значений по умолчанию + + Args: + config_file: Путь к файлу конфигурации (JSON) + + Returns: + Словарь с конфигурацией + """ + config = DEFAULT_CONFIG.copy() + + if config_file and os.path.exists(config_file): + try: + with open(config_file, 'r', encoding='utf-8') as f: + user_config = json.load(f) + config.update(user_config) + except Exception as e: + print(f"ОШИБКА: Не удалось загрузить конфигурацию из {config_file}: {e}") + sys.exit(1) + + return config + + +def create_sample_config(output_file: str): + """ + Создание примера файла конфигурации + + Args: + output_file: Путь к выходному файлу + """ + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(DEFAULT_CONFIG, f, indent=4, ensure_ascii=False) + + print(f"Пример конфигурации создан: {output_file}") + print("Отредактируйте файл и укажите ваши учетные данные") + + +# ============================================================================== +# ОСНОВНАЯ ФУНКЦИЯ +# ============================================================================== + +def main(): + """Основная функция скрипта""" + + # Парсинг аргументов командной строки + parser = argparse.ArgumentParser( + description="Автоматическое управление SSL сертификатами Let's Encrypt через API reg.ru" + ) + parser.add_argument( + "-c", "--config", + help="Путь к файлу конфигурации (JSON)", + default=None + ) + parser.add_argument( + "--create-config", + help="Создать пример файла конфигурации", + metavar="FILE" + ) + parser.add_argument( + "--obtain", + help="Получить новый сертификат", + action="store_true" + ) + parser.add_argument( + "--renew", + help="Обновить существующий сертификат", + action="store_true" + ) + parser.add_argument( + "--check", + help="Проверить срок действия сертификата", + action="store_true" + ) + parser.add_argument( + "--auth-hook", + help="Внутренний хук для DNS аутентификации (используется certbot)", + action="store_true" + ) + parser.add_argument( + "--cleanup-hook", + help="Внутренний хук для очистки DNS (используется certbot)", + action="store_true" + ) + parser.add_argument( + "-v", "--verbose", + help="Подробный вывод", + action="store_true" + ) + + args = parser.parse_args() + + # Создание примера конфигурации + if args.create_config: + create_sample_config(args.create_config) + return 0 + + # Загрузка конфигурации + config = load_config(args.config) + + # Настройка логирования + logger = setup_logging(config["log_file"], args.verbose) + + # Обработка хуков для certbot + if args.auth_hook: + # Certbot передает домен и токен через переменные окружения + domain = os.environ.get("CERTBOT_DOMAIN") + token = os.environ.get("CERTBOT_VALIDATION") + + if domain and token: + api = RegRuAPI(config["regru_username"], config["regru_password"], logger) + manager = LetsEncryptManager(config, api, logger) + success = manager.dns_challenge_hook(domain, token) + return 0 if success else 1 + else: + logger.error("CERTBOT_DOMAIN или CERTBOT_VALIDATION не установлены") + return 1 + + if args.cleanup_hook: + domain = os.environ.get("CERTBOT_DOMAIN") + token = os.environ.get("CERTBOT_VALIDATION") + + if domain and token: + api = RegRuAPI(config["regru_username"], config["regru_password"], logger) + manager = LetsEncryptManager(config, api, logger) + success = manager.dns_cleanup_hook(domain, token) + return 0 if success else 1 + else: + logger.error("CERTBOT_DOMAIN или CERTBOT_VALIDATION не установлены") + return 1 + + # Проверка прав root + if os.geteuid() != 0: + logger.error("Скрипт должен быть запущен от имени root (sudo)") + return 1 + + # Инициализация API и менеджера + api = RegRuAPI(config["regru_username"], config["regru_password"], logger) + manager = LetsEncryptManager(config, api, logger) + + # Проверка certbot + if not manager.check_certbot_installed(): + logger.error("Установите certbot: apt-get install certbot") + return 1 + + logger.info("=" * 60) + logger.info("СКРИПТ УПРАВЛЕНИЯ SSL СЕРТИФИКАТАМИ LET'S ENCRYPT") + logger.info("=" * 60) + + # Выполнение действий + if args.check: + # Только проверка срока действия + days_left = manager.check_certificate_expiry() + if days_left is None: + logger.info("Сертификат не найден. Требуется создание нового.") + return 2 + elif days_left < 30: + logger.warning(f"Сертификат истекает через {days_left} дней. Требуется обновление!") + return 1 + else: + logger.info(f"Сертификат действителен ({days_left} дней)") + return 0 + + elif args.obtain: + # Принудительное получение нового сертификата + success = manager.obtain_certificate() + if success: + manager.display_certificate_info() + reload_webserver(logger) + logger.info("Новый сертификат успешно создан") + return 0 + else: + logger.error("Не удалось получить сертификат") + return 1 + + elif args.renew: + # Обновление существующего сертификата + success = manager.renew_certificate() + if success: + manager.display_certificate_info() + reload_webserver(logger) + logger.info("Сертификат успешно обновлен") + return 0 + else: + logger.error("Не удалось обновить сертификат") + return 1 + + else: + # Автоматический режим: проверка и обновление при необходимости + days_left = manager.check_certificate_expiry() + + if days_left is None: + # Сертификат не существует + logger.info("Сертификат не найден. Создание нового...") + success = manager.obtain_certificate() + elif days_left < 30: + # Сертификат скоро истекает + logger.info(f"Сертификат истекает через {days_left} дней. Обновление...") + success = manager.renew_certificate() + else: + # Сертификат действителен + logger.info(f"Сертификат действителен ({days_left} дней). Обновление не требуется.") + manager.display_certificate_info() + return 0 + + if success: + manager.display_certificate_info() + reload_webserver(logger) + logger.info("Операция завершена успешно") + return 0 + else: + logger.error("Операция завершилась с ошибкой") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/letsencrypt_regru_dns.sh b/letsencrypt_regru_dns.sh new file mode 100644 index 0000000..88068cc --- /dev/null +++ b/letsencrypt_regru_dns.sh @@ -0,0 +1,290 @@ +#!/bin/bash + +############################################################################### +# Скрипт для создания и обновления SSL сертификата Let's Encrypt +# с использованием DNS-валидации через API reg.ru +# +# Автор: GitHub Copilot +# Дата: 27.10.2025 +# Описание: Автоматизация получения wildcard сертификата через Certbot +# с использованием DNS-01 challenge и API reg.ru +############################################################################### + +# ============================================================================== +# КОНФИГУРАЦИЯ +# ============================================================================== + +# Учетные данные API reg.ru (получить на https://www.reg.ru/user/account/) +REGRU_USERNAME="your_username" # Имя пользователя reg.ru +REGRU_PASSWORD="your_password" # Пароль от аккаунта reg.ru + +# Параметры домена и сертификата +DOMAIN="dfv24.com" # Основной домен +WILDCARD_DOMAIN="*.dfv24.com" # Wildcard домен +EMAIL="admin@dfv24.com" # Email для уведомлений Let's Encrypt + +# Директории для хранения сертификатов +CERT_DIR="/etc/letsencrypt/live/$DOMAIN" +CREDENTIALS_DIR="/etc/letsencrypt/credentials" +CREDENTIALS_FILE="$CREDENTIALS_DIR/regru.ini" + +# Логирование +LOG_FILE="/var/log/letsencrypt_regru.log" +TIMESTAMP=$(date '+%d.%m.%Y %H:%M:%S') + +# ============================================================================== +# ФУНКЦИИ +# ============================================================================== + +# Логирование сообщений +log() { + echo "[$TIMESTAMP] $1" | tee -a "$LOG_FILE" +} + +# Проверка установки необходимых пакетов +check_dependencies() { + log "Проверка зависимостей..." + + # Проверка certbot + if ! command -v certbot &> /dev/null; then + log "ОШИБКА: certbot не установлен. Установите: apt-get install certbot" + exit 1 + fi + + # Проверка curl + if ! command -v curl &> /dev/null; then + log "ОШИБКА: curl не установлен. Установите: apt-get install curl" + exit 1 + fi + + # Проверка jq для обработки JSON + if ! command -v jq &> /dev/null; then + log "ОШИБКА: jq не установлен. Установите: apt-get install jq" + exit 1 + fi + + log "Все зависимости установлены" +} + +# Создание директории для credentials +setup_credentials_dir() { + log "Настройка директории для учетных данных..." + + if [ ! -d "$CREDENTIALS_DIR" ]; then + mkdir -p "$CREDENTIALS_DIR" + chmod 700 "$CREDENTIALS_DIR" + fi +} + +# Создание файла с учетными данными для certbot-dns-regru +create_credentials_file() { + log "Создание файла с учетными данными reg.ru..." + + cat > "$CREDENTIALS_FILE" <