27 June 2016

Пример простой автоматизации letsencrypt

Nginx
Tutorial
image

Удостоверяющий центр «Let’s Encrypt» (далее просто letsencrypt) вышел из беты пару месяцев назад, пообтерся в реальных условиях, избавился от детских болезней и оброс различными клиентами. И к этому моменту выдал 5 миллионов сертификатов. Самое время внедрять, т.е. получать сертификаты на свои домены и обновлять их в автоматическом режиме. Но как внедрить так, чтобы приблизиться к любимому админскому «поставил и забыл»? Чтобы было просто получать новые сертификаты, а старые при этом обновлялись автоматом? Ну и как добавить немного безопасности в этот процесс?
Ответ под катом.

TL;DR/Quick Start/шпаргалка
  • Скачать и настроить скрипт
    sudo adduser --system --home /opt/letsencrypt le
    sudo -u le -s
    git clone https://github.com/lukas2511/letsencrypt.sh.git /opt/letsencrypt/
    mkdir /opt/letsencrypt/.acme-challenges
    echo CONTACT_EMAIL="your@email" > /opt/letsencrypt/config
    echo "ваш_домен" > /opt/letsencrypt/domains.txt
    
  • добавить в конфиг виртуального хоста nginx строчку
    location /.well-known/acme-challenge/ { alias /opt/letsencrypt/.acme-challenges/; }
    
  • запустить скрипт
    sudo -u le /opt/letsencrypt/letsencrypt.sh --cron
    
  • добавить ещё три строчки в конфиг виртуального хоста
    listen 443 ssl;
    ssl_certificate /opt/letsencrypt/certs/ваш_домен/fullchain.pem;
    ssl_certificate_key /opt/letsencrypt/certs/ваш_домен/privkey.pem;
    
  • добавить в крон пользователя le
    1 0 * * * /opt/letsencrypt/letsencrypt.sh --cron
    


Как я уже сказал, letsencrypt оброс различными клиентами, которые позволяют получить сертификат. Этих самых клиентов, кстати, уже десятки под разные системы и языки программирования. В статье будет рассказано про реализацию клиента на bash от lukas2511, letsencrypt.sh. Это один скрипт на bash, который лежит в своей папке и для работы ему нужен только openssl. Запускаться он будет под отдельным пользователем. Конечно, при желании, всегда можно ещё сильнее закрутить гайки в плане безопасности — запускать в chroot и т.д.

Сначала нужно скачать и настроить скрипт.
Предположим, что ОС — linux, веб сервер — nginx, рабочая папка скрипта — /opt/letsencrypt, а пользователь — le.
Создадим системного пользователя, из под которого будет работать скрипт. При создании системного пользователя в debian/ubuntu, ему выставляется оболочка /bin/false и назначается группа nogroup, что нам вполне подходит.
$ sudo adduser --system --home /opt/letsencrypt le

Теперь можно стать этим пользователем и все делать под ним (кроме настройки nginx).
Скачиваем скрипт и смотрим содержимое.
$ sudo -u le -s
$ git clone https://github.com/lukas2511/letsencrypt.sh.git /opt/letsencrypt/
$ ls -la /opt/letsencrypt/
total 84
drwxr-xr-x 4 le   le    4096 Jun 25 15:56 .
drwxr-xr-x 3 root root  4096 Jun 25 15:53 ..
-rw-r--r-- 1 le   le    1406 Jun 25 15:56 CHANGELOG
drwxr-xr-x 3 le   le    4096 Jun 25 15:56 docs
drwxr-xr-x 8 le   le    4096 Jun 25 15:56 .git
-rw-r--r-- 1 le   le     108 Jun 25 15:56 .gitignore
-rwxr-xr-x 1 le   le   37634 Jun 25 15:56 letsencrypt.sh
-rw-r--r-- 1 le   le    1080 Jun 25 15:56 LICENSE
-rw-r--r-- 1 le   le    3040 Jun 25 15:56 README.md
-rwxr-xr-x 1 le   le    8048 Jun 25 15:56 test.sh
-rw-r--r-- 1 le   le     107 Jun 25 15:56 .travis.yml

Для работы скрипту нужна папка, куда он будет складывать файлы для валидации доменов.
По умолчанию скрипт настроен использовать папку /opt/letsencrypt/.acme-challenges и будет падать с ошибкой, если такой папки нет.
Так же желательно создать конфиг с нужными параметрами. Параметры для работы скрипт пытается брать из файла /opt/letsencrypt/config. По умолчанию файла нет и скрипт использует значения по умолчанию, но есть хорошо документированный конфиг в папке с документацией, который можно взять за основу.
Создаем папку и копируем конфиг
$ mkdir /opt/letsencrypt/.acme-challenges
$ cp /opt/letsencrypt/docs/examples/config /opt/letsencrypt/config

Чтобы посмотреть, с какими значениями работает скрипт, его можно вызвать с ключем --env
$ /opt/letsencrypt/letsencrypt.sh --env
# letsencrypt.sh configuration
#
# !! WARNING !! No main config file found, using default config!
#
declare -- CA="https://acme-v01.api.letsencrypt.org/directory"
declare -- LICENSE="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
declare -- CERTDIR="/opt/letsencrypt/certs"
declare -- CHALLENGETYPE="http-01"
declare -- DOMAINS_TXT="/opt/letsencrypt/domains.txt"
declare -- HOOK=""
declare -- HOOK_CHAIN="no"
declare -- RENEW_DAYS="30"
declare -- ACCOUNT_KEY="/opt/letsencrypt/accounts/aHR0cHM6Ly9hY21lLXYwMS5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo/account_key.pem"
declare -- ACCOUNT_KEY_JSON="/opt/letsencrypt/accounts/aHR0cHM6Ly9hY21lLXYwMS5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo/registration_info.json"
declare -- KEYSIZE="4096"
declare -- WELLKNOWN="/opt/letsencrypt/.acme-challenges"
declare -- PRIVATE_KEY_RENEW="yes"
declare -- OPENSSL_CNF="/usr/lib/ssl/openssl.cnf"
declare -- CONTACT_EMAIL=""
declare -- LOCKFILE="/opt/letsencrypt/lock"

Описание некоторых параметров
CA — какой удостоверяющий центр использовать. Их, как минимум два — боевой (по умолчанию) и тестовый. Дело в том, что у боевого есть разные ограничения по частоте запросов и количеству доменов. В эти ограничения легко упереться при тестировании. Поэтому я рекомендую для тестовых запусков указать тестовый центр. Он работает так же, как боевой, просто генерирует невалидные сертификаты.
Вот тестовый CA:
CA="https://acme-staging.api.letsencrypt.org/directory"

CERTDIR — папка для сертификатов. Внутри неё отельные папки по имени хоста. А в этих папках уже сертификаты для каждого хоста. Нужно будет настроить nginx читать сертификаты из этих папок (см. ниже).

DOMAINS_TXT — список доменов. Одна строчка — один сертификат. В одной строчке может быть несколько доменов, тогда создается один сертификат для них. Скрипт берет первый домен, как название сертификата, а остальные домены указывает, как дополнительные. Например, для такого файла, скрипт создаст два сертификата: some.domain.com и test.com.
some.domain.com another.domain.net example.domain.org
test.com www.test.org ftp.test.net

HOOK — скрипт, который запускается при различных действиях (при валидации доменов, при генерации сертификатов и т.д.).
Скрипту передаются разные параметры: название операции, пути к новым сертификатам и т.д.
Указание своего скрипта может помочь, если вам нужно выполнять чуть больше действий на каждом шаге.
Например, сертификат нужно разложить на несколько серверов, или нужно проводить валидацию домена через dns, а не через файл.
Скрипт, который ничего не делает, но содержит массу комментариев, лежит по адресу /opt/letsencrypt/docs/examples/hook.sh

RENEW_DAYS — через сколько дней обновлять сертификат. Максимум 90, по умолчанию 30.

CONTACT_EMAIL — рабочий email администратора.

Я рекомендую указать в конфиге свой email в CONTACT_EMAIL и на время тестов прописать тестовый CA.
Установка и первоначальная настройка скрипта на этом завершены. Теперь можно получать сертификаты.

Для начала получим сертифкат для одного домена: letest.lexore.net
В nginx для теста сделаем простой конфиг виртуального хоста, который будет выводить протокол, http или https.
server {
    listen 80;
    server_name letest.lexore.net;
    location /.well-known/acme-challenge/ { alias /opt/letsencrypt/.acme-challenges/; }

    location / {
         default_type text/plain;
         return 200 "scheme: $scheme";
    }
}

Ключевой параметр: location /.well-known/acme-challenge/
В папке /opt/letsencrypt/.acme-challenges/ создаются файлы для подтверждения, что вы управляете сайтом.
Они должны быть доступны по адресу имя_сайта/.well-known/acme-challenge/, иначе сертификат не подпишут.
Названия файлов генерируются случайным образом, поэтому проще открыть доступ ко всей папке.
В конце скрипт удаляет созданный файл, так что папка не будет захламляться.

Перезагрузим nginx и проверим сайт:
$ curl -i letest.lexore.net
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 26 Jun 2016 13:13:18 GMT
Content-Type: text/plain
Content-Length: 12
Connection: keep-alive

scheme: http

Теперь нужно прописать имя хоста в domains.txt и запустить сам скрипт
$ echo letest.lexore.net > /opt/letsencrypt/domains.txt
$ /opt/letsencrypt/letsencrypt.sh --cron
# INFO: Using main config file /opt/letsencrypt/config
Processing letest.lexore.net
 + Signing domains...
 + Creating new directory /opt/letsencrypt/certs/letest.lexore.net ...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for letest.lexore.net...
 + Responding to challenge for letest.lexore.net...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Done!

Сертификат готов, файлы сертификата и ключа лежат в папке "/opt/letsencrypt/certs/letest.lexore.net".
Осталось добавить настройки в nginx. Нужно добавить следующие строки в конфиг виртуального хоста:
listen   443 ssl;
ssl_certificate /opt/letsencrypt/certs/letest.lexore.net/fullchain.pem;
ssl_certificate_key /opt/letsencrypt/certs/letest.lexore.net/privkey.pem;

После перезагрузки nginx, можно пробовать сайт в браузере.
Если в конфиге скрипта был указан тестовый CA, браузер заругается на сертификат.
Как выглядит такой сертификат в firefox
image

Но это все равно значит, что скрипт и nginx настроены правильно.
Теперь нужно просто поменять CA в конфиге на боевое значание и запустить скрипт ещё раз, добавив параметр --force.
Без этого параметра скрипт не станет заново генерировать сертификат, т.к. ещё не подошел срок устаревания, указанный в конфиге.
le@endor:~$ /opt/letsencrypt/letsencrypt.sh --cron --force
# INFO: Using main config file /opt/letsencrypt/config
Processing letest.lexore.net
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Sep 24 12:13:00 2016 GMT (Longer than 80 days). Ignoring because renew was forced!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for letest.lexore.net...
 + Responding to challenge for letest.lexore.net...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Done!

После запуска скрипта и перезагрузки nginx у сайта появится правильный сертификат.
Сертификат готов, https работает.
Как выглядит правильный сертификат


Пару слов про несколько доменов.
Один сертификат может использоваться для нескольких доменов. Например, вот как это будет выглядеть, если добавить subdomain.letest.lexore.net.
Запуск скрипта:
$ /opt/letsencrypt/letsencrypt.sh --cron --force
# INFO: Using main config file /opt/letsencrypt/config
Processing letest.lexore.net with alternative names: subdomain.letest.lexore.net
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: letest.lexore.net
 + Configured names: letest.lexore.net subdomain.letest.lexore.net
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Sep 24 12:48:00 2016 GMT (Longer than 80 days). Ignoring because renew was forced!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for letest.lexore.net...
 + Requesting challenge for subdomain.letest.lexore.net...
 + Responding to challenge for letest.lexore.net...
 + Challenge is valid!
 + Responding to challenge for subdomain.letest.lexore.net...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Done!

Правка конфига nginx, перезапуск, и вот новый сертификат работает уже на два домена.
Скриншот такого сертификата в firefox


Домены не обязаны быть как-то связаны, можно сделать один домен для domain.com и ftp.example.net.
Максимум можно указать 100 доменов в одном сертификате.
Кому-то этого может хватить, чтобы обойтись одним сертификатом для всех сайтов на сервере.
Правда, этот сертификат придется пересоздавать на каждый новый домен в списке, и можно упереться в лимиты.

А теперь самое приятное — автоматизация.
Для того, чтобы скрипт сам обновлял все сертификаты из файла domains.txt и вы забыли про манипуляции руками, нужно добавить одну строчку в крон пользователя le:
0 1 * * * /opt/letsencrypt/letsencrypt.sh --cron

Таким образом, алгоритм перевода последующих сайтов на https:
  • добавить location /.well-known/acme-challenge/ { alias /opt/letsencrypt/.acme-challenges/; }
  • добавить ваш_домен в domains.txt
  • запустить скрипт /opt/letsencrypt/letsencrypt.sh --cron
  • добавить настройки ssl
    listen   443 ssl;
    ssl_certificate /opt/letsencrypt/certs/ваш_домен/fullchain.pem;
    ssl_certificate_key /opt/letsencrypt/certs/ваш_домен/privkey.pem;
    


Это не единственный способ автоматизации, многие клиенты letsencrypt позволяют свести всю работу к запуску одного скрипта по крону.
Данный скрипт мне понравился за простоту- всю работу делает один скрипт на bash.
А так же, за свои дополнительные возможности — кроме простой автоматизации, он из коробки поддерживает «сложную» автоматизацию, запуск своих скриптов и переопределение параметров.
Например, я уже упомянул про параметр HOOK в конфиге, который позволяет запускать свой скрипт.
Так же из коробки есть параметр CONFIG_D — папка, в которой будут запускаться все .sh скрипты для переопределения параметров основного конфига.
Плюс, поддержка разных «аккаунтов» через указание разных ACCOUNTDIR — папок с приватными ключами для подписи запросов.
Мне кажется, это позволит использовать скрипт в больших и сложных инфраструктурах.
Tags: letsencrypt
Hubs: Nginx
+31
62.7k 346
Comments 38
Ads
Top of the day