- PVSM.RU - https://www.pvsm.ru -

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

image

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

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 оброс различными клиентами, которые позволяют получить сертификат. Этих самых клиентов, кстати, уже десятки [2] под разные системы и языки программирования. В статье будет рассказано про реализацию клиента на bash от lukas2511, letsencrypt.sh [3]. Это один скрипт на 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 — какой удостоверяющий центр использовать. Их, как минимум два — боевой (по умолчанию) и тестовый [4]. Дело в том, что у боевого есть разные ограничения [5] по частоте запросов и количеству доменов. В эти ограничения легко упереться при тестировании. Поэтому я рекомендую для тестовых запусков указать тестовый центр. Он работает так же, как боевой, просто генерирует невалидные сертификаты.
Вот тестовый 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 работает.

Как выглядит правильный сертификат

Пример простой автоматизации letsencrypt - 3 [6]

Пару слов про несколько доменов.
Один сертификат может использоваться для нескольких доменов. Например, вот как это будет выглядеть, если добавить 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

Пример простой автоматизации letsencrypt - 4 [7]

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

А теперь самое приятное — автоматизация.
Для того, чтобы скрипт сам обновлял все сертификаты из файла 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 — папок с приватными ключами для подписи запросов.
Мне кажется, это позволит использовать скрипт в больших и сложных инфраструктурах.

Автор: lexore

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/nginx/144526

Ссылки в тексте:

[1] 5 миллионов: https://letsencrypt.org/2016/06/22/https-progress-june-2016.html

[2] десятки: https://letsencrypt.org/docs/client-options/

[3] letsencrypt.sh: https://github.com/lukas2511/letsencrypt.sh

[4] тестовый: https://community.letsencrypt.org/t/testing-against-the-lets-encrypt-staging-environment/6763

[5] разные ограничения: https://community.letsencrypt.org/t/rate-limits-for-lets-encrypt/6769

[6] Image: https://habrastorage.org/files/f7d/e73/c77/f7de73c77d1748149b881a547817609e.png

[7] Image: https://habrastorage.org/files/5cc/61f/f5b/5cc61ff5bb3f472aa3b5c20e0321740a.png

[8] Источник: https://habrahabr.ru/post/304174/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best