ADG Оставить заявку
Блог Инфраструктура 4 мин чтения

certbot + Ansible: TLS-сертификат как одна задача в плейбуке

К концу 2015-го certbot оформился в нормальный инструмент. Написали Ansible-роль - получение и обновление сертификатов теперь одна задача в плейбуке.

Контекст момента

Let's Encrypt публичная бета в разгаре - certbot стабилизировался и стал стандартным инструментом для автоматизации TLS

В ноябре мы перевели десяток сервисов на Let's Encrypt за вечер через certbot вручную. Команда за командой, хост за хостом. Работало, но это был не процесс, а «разовая акция с конспектом». К концу декабря накопилось достаточно поводов чтобы сделать это нормально.

Повод первый: новый проект, стандартное managed-сопровождение, пять доменов - и инженер потратил полчаса на certbot вместо того чтобы эти полчаса делать что-то менее механическое. Повод второй: в другом проекте certbot обновился, немного поменялись пути, cron-команда сломалась тихо и без алертов. Выяснилось через две недели. Итого: хватит, пишем роль.

Что было до роли

Схема была незамысловатая: берём чеклист из нашего внутреннего вики, ставим certbot, получаем сертификат вручную, добавляем cron строчку, добавляем location в nginx. Это занимало 20-30 минут на домен, при наличии чеклиста всё делалось правильно. Но чеклист - это не инфраструктура. Это надежда на то что человек ничего не пропустит.

Ещё проблема: certbot renew после обновления требует перезагрузки nginx, но не знает как это сделать. Значит надо делать reload отдельной командой после cron или оборачивать в скрипт. Это просто забывалось.

Ansible-роль: что внутри

Роль решает три задачи: установить certbot, получить сертификат для домена, настроить автоматическое обновление с hook-ом на reload nginx.

Структура переменных минимальная:

# group_vars или host_vars
certbot_email: "ops@example.ru"
certbot_domains:
  - name: "example.ru"
    webroot: "/var/www/example.ru"
    aliases:
      - "www.example.ru"
  - name: "api.example.ru"
    webroot: "/var/www/api"
    aliases: []

Задача получения сертификата выглядит так:

- name: obtain certificate
  command: >
    certbot certonly --webroot
    -w {{ item.webroot }}
    -d {{ item.name }}
    {% for alias in item.aliases %}-d {{ alias }} {% endfor %}
    --email {{ certbot_email }}
    --agree-tos
    --non-interactive
  args:
    creates: "/etc/letsencrypt/live/{{ item.name }}/fullchain.pem"
  with_items: "{{ certbot_domains }}"

Флаг creates делает задачу идемпотентной - если сертификат уже есть, certbot не запускается. Это важно: ansible-playbook запускается регулярно, и не нужно чтобы он каждый раз трогал сертификаты.

Для cron роль раскатывает два файла. Первый - сам таймер:

0 4 * * 2 certbot renew --quiet && systemctl reload nginx

Второй - скрипт проверки что certbot не упал тихо. Просто смотрит на дату последнего выданного сертификата и пишет в syslog если что-то подозрительно старое. Не rocket science, но лучше чем ничего.

nginx и acme-challenge

Отдельная маленькая задача роли - убедиться что в nginx есть правильный location для валидации. Добавляется один раз в общий конфиг-сниппет и подключается инклюдом:

location /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
    try_files $uri =404;
}

Все домены на хосте используют один и тот же root для challenge-файлов, certbot об этом знает через --webroot-path. Это чище чем держать разные директории под разные сайты.

Что не автоматизировали

Два случая выпали из роли намеренно.

Первое - standalone-режим. Когда certbot сам поднимает временный HTTP-сервер на 80-м порту. Это требует останавливать nginx, а останавливать nginx в плейбуке без ручного подтверждения неуютно на production-хосте. Пока оставили как ручной шаг с комментарием в README роли.

Второе - не-nginx стеки. Один клиент держит Apache, у другого Jetty-обёртка (Nexus). Для них роль не работает в текущем виде - hook на reload nginx сломает ситуацию или ничего не сделает. Добавили переменную certbot_reload_command с дефолтом systemctl reload nginx - но тестировали только на nginx.

Как выглядит теперь

Новый проект, managed-сопровождение, пять доменов. В плейбук добавляется один блок ролей:

- role: letsencrypt
  vars:
    certbot_email: "ops@client.ru"
    certbot_domains:
      - { name: "client.ru", webroot: "/var/www/client", aliases: ["www.client.ru"] }
      - { name: "api.client.ru", webroot: "/var/www/api", aliases: [] }
      # ... ещё три

Запуск плейбука - сертификаты получены, cron настроен, nginx перезапущен с нужными путями. Инженер идёт дальше.

Про надёжность certbot на этом этапе

Certbot за последние месяцы заметно повзрослел. Когда мы его первый раз трогали в сентябре в закрытой бете - это был скрипт с кучей зависимостей через pip, который устанавливался в virtualenv и вёл себя иногда непредсказуемо. Сейчас установка через letsencrypt-auto стабилизировалась, скрипт сам разрешает зависимости, поведение предсказуемое.

Одно наблюдение: 90-дневный срок сертификата - это и страховка и тест. Если автоматическое обновление сломалось, у вас есть окно чтобы это заметить и починить до того как сайт упадёт. Три месяца достаточно. Но это работает только если мониторинг на дату истечения реально настроен. У нас - через Zabbix с пороговым алертом за 20 дней.


На хостах managed-сопровождения роль уже раскатана на большинстве серверов. Платные сертификаты остаются только где нужен EV - и таких проектов единицы.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.