Получение сертификата Let’s Encrypt с помощью Ansible в Ubuntu 18.04

Современное управление инфраструктурой лучше всего организовать с помощью автоматизированных процессов и инструментов. Получить сертификат Let’s Encrypt с помощью стандартного клиента Certbot можно быстро и просто, но такую задачу необходимо выполнять вручную при вводе серверов в эксплуатацию. Это несложно, если нужен один сертификат для одного сервера, но при развертывании большого кластера это займет слишком много времени и сил.

К счастью, существует много инструментов управления конфигурацией, и Ansible один из них. Ansible может полностью автоматизировать процесс получения сертификата и сделает эту задачу воспроизводимой. Если в будущем вам придется пересобирать или обновлять свой сервер, вы можете просто запустить свой плейбук Ansible, а не выполнять задачу вручную.

Читайте также: Как работать с Ansible: простая и удобная шпаргалка

В этом мануале мы напишем плейбук Ansible, который будет автоматически получать сертификат Let’s Encrypt для хоста.

Требования

Для работы вам нужны два сервера Ubuntu 18.04, настроенные по этому мануалу.

Первый сервер будет сервером Ansible: на нем мы установим Ansible. Для этих целей вы можете использовать локальную машину. На этом сервере вам нужно иметь рабочую установку Ansible. Инструкции есть в мануале Установка и настройка Ansible в Ubuntu 18.04

Второй сервер будет хостом Ansible. Это машина, которую Ansible настроит и для которой он получит сертификаты. Также на этом сервере будет работать веб-сервер для обслуживания файлов проверки сертификатов.

На хосте нужно иметь:

  • Доменное имя, для которого вы можете создать сертификат TLS, со всеми необходимыми DNS записями, указывающими на хост. В нашем примере плейбук создаст сертификат для условных доменов your-domain и www.your-domain, вместо которых вы должны указать свои данные.
  • Веб-сервер с доступом в Интернет, работающий по порту 80 (HTTP). Инструкции можно найти в мануале Установка веб-сервера Apache в Ubuntu 18.04. Также можно использовать Nginx или любой другой веб-сервер.

Когда серверы будут готовы, войдите  на сервер 1 (сервер Ansible) как пользователь без прав root, чтобы начать работу.

1: Настройка модуля Ansible для Let’s Encrypt

Ansible имеет встроенный модуль по имени letsencrypt, который позволяет получать доверенные сертификаты TLS по протоколу ACME (Automated Certificate Management Environment).

Для начала нужно добавить конфигурационный файл с переменными хоста, необходимыми для использования модуля.

Примечание: Модуль letsencrypt был переименован в acme_certificate, начиная с версии Ansible 2.6. Имя letsencrypt теперь является псевдонимом для acme_certificate, поэтому оно все равно будет работать, но лучше все же использовать acme_certificate, чтобы в будущем в плейбуках не возникли конфликты. Вы можете проверить свою версию Ansible с помощью команды ansible —version. На момент написания мануала репозитории Ubuntu 18.04 еще не поддерживают acme_certificate.

Сначала создайте каталог host_vars на сервере Ansible:

sudo mkdir /etc/ansible/host_vars

Затем создайте в каталоге /etc/ansible/host_vars новый файл, присвоив ему имя вашего хоста (сервер 2). В этом примере мы назовем его host1:

sudo nano /etc/ansible/host_vars/host1

Следующий пример конфигурации включает в себя все, что вам нужно для начала работы: метод валидации, адрес сервера, адрес электронной почты, на который будут отправляться напоминания об истечении срока сертификата, и каталоги, в которых будут храниться ключи и сертификаты Let’s Encrypt.

Скопируйте этот пример конфигурации и вставьте его в файл:

---
acme_challenge_type: http-01
acme_directory: https://acme-v02.api.letsencrypt.org/directory
acme_version: 2
acme_email: certificate-reminders@your-domain
letsencrypt_dir: /etc/letsencrypt
letsencrypt_keys_dir: /etc/letsencrypt/keys
letsencrypt_csrs_dir: /etc/letsencrypt/csrs
letsencrypt_certs_dir: /etc/letsencrypt/certs
letsencrypt_account_key: /etc/letsencrypt/account/account.key
domain_name: your-domain

Сохраните и закройте файл.

Не забудьте указать в конфигурации ваше доменное имя и адрес электронной почты. Вы можете использовать любой адрес – он не обязательно должен быть на your-domain.

Некоторые из заданных путей к каталогам и файлам могут еще не существовать на вашем сервере. Это нормально. Первая часть плейбука будет сосредоточена на создании этих каталогов и настройке доступа к ним.

Только что вы добавили необходимые переменные конфигурации в свой файл инвентаря Ansible. Теперь можно начать работу над плейбуком для получения сертификата.

2: Создание каталогов и ключей Let’s Encrypt

Теперь пора написать задачи Ansible, которые создадут ряд каталогов Let’s Encrypt, присвоят правильные права доступа и создадут ключ учетной записи Let’s Encrypt.

Сначала создайте новый плейбук letsencrypt-issue.yml на вашем сервере Ansible, это нужно сделать в новом каталоге, например, в /home/user/ansible-playbooks:

cd ~
mkdir ansible-playbooks
cd ansible-playbooks
nano letsencrypt-issue.yml

Прежде чем вы сможете начать писать задачи Ansible, вам нужно указать хосты и связанные с ними настройки. Отредактируйте настройки в соответствии с тем, как вы ссылались на свои хосты в этом мануале (его вы должны были выполнить, чтобы подготовить установку Ansible). Затем добавьте следующее в начало файла:

---
- hosts: "host1"
tasks:

Теперь вы можете приступать к написанию задач, первой из которых является создание каталогов файловой системы, необходимых для хранения файлов Let’s Encrypt. Добавьте следующую задачу Ansible в файл под приведенными выше строками:

...
- name: "Create required directories in /etc/letsencrypt"
file:
path: "/etc/letsencrypt/{{ item }}"
state: directory
owner: root
group: root
mode: u=rwx,g=x,o=x
with_items:
- account
- certs
- csrs
- keys

Эта задача Ansible создаст каталоги account, certs, csrs и keys в /etc/letsencrypt, где будут храниться файлы, необходимые для получения сертификатов.

Владельцем каталогов будет root. Также эти строки установят права доступа u=rwx,g=x,o=x, чтобы только root имел право на чтение и запись в этих файлах. Рекомендуется установить именно такие привилегии, поскольку каталоги будут содержать закрытые ключи, запросы на подпись сертификатов (CSR) и подписанные сертификаты, а эти данные должны быть конфиденциальными.

Затем необходимо создать ключ учетной записи Let’s Encrypt. Вы будете использовать его для самоидентификации в сервисе Let’s Encrypt.

Добавьте следующую задачу в плейбук:

...
- name: "Generate a Let's Encrypt account key"
shell: "if [ ! -f {{ letsencrypt_account_key }} ]; then openssl genrsa 4096 | sudo tee {{ letsencrypt_account_key }}; fi"

Ключ учетной записи не нужно создавать заново при обновлении сертификатов, поэтому вы также можете добавить проверку существующего ключа, чтобы он не был перезаписан:

if [ ! -f {{ letsencrypt_account_key }} ];

Мы еще будем работать с letsencrypt-issue.yml, поэтому пока не закрывайте этот файл.

Только что вы создали свой плейбук и настроили начальную конфигурацию и базовые задачи, которые подготавливают сервер к получению сертификата. После этого нужно создать дополнительные задачи для генерации закрытого ключа и CSR.

3: Генерирование закрытого ключа и запроса на подпись сертификата

Теперь мы напишем задачи, которые будут генерировать требуемый закрытый ключ и запрос на подпись сертификата.

Первая задача в этом разделе сгенерирует необходимый закрытый ключ для вашего сертификата. Добавьте следующие строки в конец своего плейбука letsencrypt-issue.yml:

...
- name: "Generate Let's Encrypt private key"
shell: "openssl genrsa 4096 | sudo tee /etc/letsencrypt/keys/{{ domain_name }}.key"

Все поддомены одного домена будут помещены в тот же сертификат с помощью Subject Alternate Name (SANs), поэтому на данный момент вам нужно только сгенерировать один закрытый ключ.

Следующая задача будет использоваться для создания запроса на подпись сертификата (CSR), который вы хотите получить. Запрос передается в Let’s Encrypt для проверки и выдачи сертификата.

Добавьте в плейбук такие строки:

...
- name: "Generate Let's Encrypt CSR"
shell: "openssl req -new -sha256 -key /etc/letsencrypt/keys/{{ domain_name }}.key -subj \"/CN={{ domain_name }}\" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf \"\n[SAN]\nsubjectAltName=DNS:{{ domain_name }},DNS:www.{{ domain_name }}\")) | sudo tee /etc/letsencrypt/csrs/{{ domain_name }}.csr"
args:
executable: /bin/bash

Эта задача создаст запрос на подпись сертификата для вашего домена с поддоменом www.

Далее мы создадим задачи, которые начнут процесс проверки и выдачи сертификатов.

4: Запуск процесса проверки ACME

На этом этапе мы добавим задачу для отправки запроса на подпись сертификата в Let’s Encrypt, используя выходные файлы задачи, созданной в разделе 3. У вас появятся файлы challenge, которые нужны веб-серверу, чтобы подтвердить право собственности на домен и поддомен, для которого вы запрашиваете сертификат.

Следующая задача подаст CSR для вашего домена. Добавьте ее в конец плейбука:

...
- name: "Begin Let's Encrypt challenges"
letsencrypt:
acme_directory: "{{ acme_directory }}"
acme_version: "{{ acme_version }}"
account_key_src: "{{ letsencrypt_account_key }}"
account_email: "{{ acme_email }}"
terms_agreed: 1
challenge: "{{ acme_challenge_type }}"
csr: "{{ letsencrypt_csrs_dir }}/{{ domain_name }}.csr"
dest: "{{ letsencrypt_certs_dir }}/{{ domain_name }}.crt"
fullchain_dest: "{{ letsencrypt_certs_dir }}/fullchain_{{ domain_name }}.crt"
remaining_days: 91
register: acme_challenge_your_domain

Эта задача широко использует переменные, которые вы настроили в разделе1. Она регистрирует переменную, содержащую challenge-файлы ACME, которые вы будете использовать на следующем этапе. Вам нужно вручную изменить имя переменной (она должна содержать your-domain), но все точки нужно заменить на _, так как точки в имени переменной использовать нельзя. Например, переменная example.com называется acme_challenge_example_com.

Мы продолжим работу с letsencrypt-issue.yml, поэтому пока не закрывайте этот файл.

Мы написали задачу для отправки CSR в Let’s Encrypt. Затем нужно создать задачу, которая с помощью файлов ACME завершит процесс проверки сертификата.

5: Использование challenge-файлов ACME

На данном этапе нам нужно создать задачу Ansible для чтения и применения challenge-файлов ACME. Эти файлы подтверждают, что вы имеете право получить сертификат для запрошенных доменов и поддоменов.

Файлы ACME должны размещаться на веб-сервере, прослушивающем порт 80, по пути /.well-known/acme-challenge/ для домена или субдомена, для которого вы запрашиваете сертификат. То есть для проверки запроса сертификата для www.your-domain файл ACME должен быть доступен в Интернете по следующему пути: http://www.your-domain/.well-known/acme-challenge .

Способ помещения этих файлов в требуемые места полностью зависит от настроек вашего текущего веб-сервера. Давайте предположим, что ваш веб-сервер (согласно требованиям) обслуживает файлы из каталога /var/www/html. Нам нужно настроить задачу соответствующим образом, чтобы обеспечить совместимость с настройками веб-сервера.

Сначала добавьте в конец плейбука задачу, которая создает структуру каталогов .well-known/acme-challenge/, необходимую для обслуживания файлов:

...
- name: "Create .well-known/acme-challenge directory"
file:
path: /var/www/html/.well-known/acme-challenge
state: directory
owner: root
group: root
mode: u=rwx,g=rx,o=rx

Обязательно соответствующим образом отредактируйте путь, если вместо /var/www/html для обслуживания файлов вы используете другой каталог.

Затем нужно применить challenge файлы ACME, которые были сохранены в переменной acme_challenge_your-domain. Для этого добавьте задачу:

...
- name: "Implement http-01 challenge files"
copy:
content: "{{ acme_challenge_your_domain['challenge_data'][item]['http-01']['resource_value'] }}"
dest: "/var/www/html/{{ acme_challenge_your_domain['challenge_data'][item]['http-01']['resource'] }}"
owner: root
group: root
mode: u=rw,g=r,o=r
with_items:
- "{{ domain_name }}"
- "www.{{ domain_name }}"

Обратите внимание, в задаче вам нужно вручную настроить имя переменной acme_challenge_your_domain: для этого укажите имя переменной ACME (acme_challenge_), а за ним ваш домен, но все точки нужно заменить на _. Эта задача Ansible копирует файлы ACME из переменной в путь . well-known/acme-challenge на вашем веб-сервере. Это позволит Let’s Encrypt получить файлы и подтвердить ваше право собственности на домен и, соответственно, на получение сертификата.

Теперь нужно завершить процесс проверки ACME и получить подписанный сертификат.

6: Получение сертификата

На этом этапе мы напишем задачу, которая активирует Let’s Encrypt для проверки отправленных вами файлов ACME с запросами, что позволит вам получить подписанные сертификаты.

Следующая задача проверяет файлы ACME и сохраняет подписанные сертификаты по указанным путям. Добавьте в конец плейбука следующее:

...
- name: "Complete Let's Encrypt challenges"
letsencrypt:
acme_directory: "{{ acme_directory }}"
acme_version: "{{ acme_version }}"
account_key_src: "{{ letsencrypt_account_key }}"
account_email: "{{ acme_email }}"
challenge: "{{ acme_challenge_type }}"
csr: "{{ letsencrypt_csrs_dir }}/{{ domain_name }}.csr"
dest: "{{ letsencrypt_certs_dir }}/{{ domain_name }}.crt"
chain_dest: "{{ letsencrypt_certs_dir }}/chain_{{ domain_name }}.crt"
fullchain_dest: "{{ letsencrypt_certs_dir }}/fullchain_{{ domain_name }}"
data: "{{ acme_challenge_your_domain }}"

Как и в разделе 4, в этой задаче используются переменные, настроенные в разделе 1. После выполнения задачи подписанный сертификат сохраняется по указанным путям, что позволит вам начать использовать его для своего приложения или сервиса.

Обратите внимание, вам нужно вручную настроить значение data в задаче, чтобы задать имя переменной ACME (как в разделе 5).

Ниже приводится полный код плейбука, в котором перечислены все добавленные задачи:

- hosts: "host1"
tasks:
- name: "Create required directories in /etc/letsencrypt"
file:
path: "/etc/letsencrypt/{{ item }}"
state: directory
owner: root
group: root
mode: u=rwx,g=x,o=x
with_items:
- account
- certs
- csrs
- keys
- name: "Generate a Let's Encrypt account key"
shell: "if [ ! -f {{ letsencrypt_account_key }} ]; then openssl genrsa 4096 | sudo tee {{ letsencrypt_account_key }}; fi"
- name: "Generate Let's Encrypt private key"
shell: "openssl genrsa 4096 | sudo tee /etc/letsencrypt/keys/{{ domain_name }}.key"
- name: "Generate Let's Encrypt CSR"
shell: "openssl req -new -sha256 -key /etc/letsencrypt/keys/{{ domain_name }}.key -subj \"/CN={{ domain_name }}\" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf \"\n[SAN]\nsubjectAltName=DNS:{{ domain_name }},DNS:www.{{ domain_name }}\")) | sudo tee /etc/letsencrypt/csrs/{{ domain_name }}.csr"
args:
executable: /bin/bash
- name: "Begin Let's Encrypt challenges"
letsencrypt:
acme_directory: "{{ acme_directory }}"
acme_version: "{{ acme_version }}"
account_key_src: "{{ letsencrypt_account_key }}"
account_email: "{{ acme_email }}"
terms_agreed: 1
challenge: "{{ acme_challenge_type }}"
csr: "{{ letsencrypt_csrs_dir }}/{{ domain_name }}.csr"
dest: "{{ letsencrypt_certs_dir }}/{{ domain_name }}.crt"
fullchain_dest: "{{ letsencrypt_certs_dir }}/fullchain_{{ domain_name }}.crt"
remaining_days: 91
register: acme_challenge_your_domain
- name: "Create .well-known/acme-challenge directory"
file:
path: /var/www/html/.well-known/acme-challenge
state: directory
owner: root
group: root
mode: u=rwx,g=rx,o=rx
- name: "Implement http-01 challenge files"
copy:
content: "{{ acme_challenge_your_domain['challenge_data'][item]['http-01']['resource_value'] }}"
dest: "/var/www/html/{{ acme_challenge_your_domain['challenge_data'][item]['http-01']['resource'] }}"
owner: root
group: root
mode: u=rw,g=r,o=r
with_items:
- "{{ domain_name }}"
- "www.{{ domain_name }}"
- name: "Complete Let's Encrypt challenges"
letsencrypt:
acme_directory: "{{ acme_directory }}"
acme_version: "{{ acme_version }}"
account_key_src: "{{ letsencrypt_account_key }}"
account_email: "{{ acme_email }}"
challenge: "{{ acme_challenge_type }}"
csr: "{{ letsencrypt_csrs_dir }}/{{ domain_name }}.csr"
dest: "{{ letsencrypt_certs_dir }}/{{ domain_name }}.crt"
chain_dest: "{{ letsencrypt_certs_dir }}/chain_{{ domain_name }}.crt"
fullchain_dest: "{{ letsencrypt_certs_dir }}/fullchain_{{ domain_name }}"
data: "{{ acme_challenge_your_domain }}"

Сохраните и закройте файл.

Вы добавили все задачи в плейбук, теперь с его помощью можно автоматически получить доверенный сертификат. Давайте запустим плейбук для хоста, чтобы убедиться, что он работает.

7: Запуск плейбука

Записав все необходимые задачи, вы можете запустить плейбук для хоста Ansible (сервера 2), чтобы получить сертификат.

Со своего сервера 1 вы можете запустить плейбук с помощью команды:

ansible-playbook letsencrypt-issue.yml

Команда запустит плейбук и выполнит все задачи в порядке очереди. Вы получите примерно такой вывод:

PLAY [host1] **********************************************************************************
TASK [Gathering Facts] ************************************************************************
ok: [host1]
TASK [Create required directories in /etc/letsencrypt] ****************************************
changed: [host1] => (item=account)
changed: [host1] => (item=certs)
changed: [host1] => (item=csrs)
changed: [host1] => (item=keys)
TASK [Generate a Let's Encrypt account key] ***************************************************
changed: [host1]
TASK [Generate Let's Encrypt private key] *****************************************************
changed: [host1]
TASK [Generate Let's Encrypt CSR] *************************************************************
changed: [host1]
TASK [Begin Let's Encrypt challenges] *********************************************************
changed: [host1]
TASK [Create .well-known/acme-challenge directory] ********************************************
changed: [host1]
TASK [Implement http-01 challenge files] ******************************************************
changed: [host1] => (item=your-domain)
changed: [host1] => (item=www.your-domain)
TASK [Complete Let's Encrypt challenges] ******************************************************
changed: [host1]
PLAY RECAP ************************************************************************************
host1                      : ok=9    changed=8    unreachable=0    failed=0

Если во время работы плейбука обнаружатся какие-либо ошибки, они будут выведены на экран.

После выполнения плейбука ваш доверенный сертификат Let’s Encrypt будет сохранен в каталоге /etc/letsencrypt/certs на вашем хосте. Вы можете использовать сертификат вместе с закрытым ключом в /etc/letsencrypt/keys для защиты соединений с вашим веб-сервером, почтовым сервером и т. д.

Сертификаты Let’s Encrypt действительны в течение 90 дней. Вы получите напоминания об обновлении по электронной почте (на адрес, указанный в разделе 1). Чтобы продлить сертификат, вы можете снова запустить свой плейбук. Не забудьте проверить, что все сервисы, использующие ваш сертификат, получили доступ к новому сертификату – иногда возникает необходимость установить его вручную, переместить в определенный каталог или перезапустить сервис, чтобы он правильно принял новый сертификат.

Заключение

Теперь у вас есть плейбук, который автоматически получает доверенные сертификаты для ваших хостов. Вы можете расширить свой плейбук, чтобы получить сертификаты для большего количества хостов. Вы даже можете создать главный сервер ACME, который будет централизованно выдавать сертификаты и распространять их на веб-серверы.

Читайте также:

Tags: , , , ,