如何在Ubuntu18.04上使用Ansible获取Let'sEncrypt证书
作为 Write for DOnations 计划的一部分,作者选择了 Electronic Frontier Foundation 来接受捐赠。
介绍
现代基础设施管理最好使用自动化流程和工具来完成。 使用标准 Certbot 客户端获取 Let's Encrypt 证书既快捷又简单,但通常在调试服务器时必须手动完成。 这对于单个服务器设置是可以管理的,但在部署更大的机群时可能会变得乏味。
使用诸如 Ansible 之类的配置管理工具来获取证书,使此任务完全自动化且可重现。 如果您必须重建或更新您的服务器,您只需运行 Ansible playbook,而不必再次手动执行这些步骤。
在本教程中,您将编写一个 Ansible 剧本来为 Ansible 主机自动获取 Let's Encrypt 证书。
先决条件
要完成本教程,您需要:
- 按照 Initial Server Setup 和 Ubuntu 18.04 设置两台 Ubuntu 18.04 服务器,包括一个 sudo 非 root 用户。
第一个服务器将用作您的 Ansible 服务器,在本教程中我们将其称为 Ansible 服务器。 这是 Ansible 将运行以将命令发送到主机的地方。 或者,您可以使用本地计算机或任何其他将您的 Ansible 清单配置为 Ansible 服务器 的计算机。
在您的 Ansible 服务器 上,您需要:
- 按照 如何在 Ubuntu 18.04 上安装和配置 Ansible,正确配置的 Ansible 安装能够连接到您的 Ansible 主机。
第二台服务器将用作您的 Ansible 主机,我们将在本教程中将其称为 主机 。 这是您希望在其上配置和颁发证书的机器。 这台机器还将运行一个 Web 服务器来提供证书颁发验证文件。
在您的 主机 上,您需要:
- 您有资格为其获取 TLS 证书的域名,所需的 DNS 记录配置为指向您的 Ansible 主机 。 在此特定示例中,playbook 将获取对
your-domain
和www.your-domain
有效的证书,但如果需要,可以针对其他域或子域进行调整。 - 可通过端口
80
(HTTP) 从 Internet 访问的 Web 服务器,例如按照 如何在 Ubuntu 18.04 上安装 Apache Web 服务器的步骤 1、2 和 3。 这也可以是 Nginx 服务器,或任何其他合适的 Web 服务器软件。
准备好这些后,以非 root 用户身份登录到您的 Ansible 服务器 开始。
第 1 步 — 配置 Let's Encrypt Ansible 模块的设置
Ansible 有一个名为 letsencrypt
的内置模块,它允许您使用 ACME(Automated Certificate Management Environment)协议获取有效的 TLS 证书。
在这第一步中,您将添加一个主机变量配置文件来定义使用该模块所需的配置变量。
注意: letsencrypt
模块已从 Ansible 2.6 起重命名为 acme_certificate。 letsencrypt
名称现在是 acme_certificate
的别名,因此仍然可以使用,但您希望使用 acme_certificate
来代替,以确保您的剧本经得起未来的考验。 您可以使用 ansible --version
检查您的 Ansible 版本。 在撰写本教程时,Ubuntu 18.04 Apt 存储库还不支持 acme_certificate
。
首先,在您的 Ansible 服务器 上创建 host_vars
Ansible 目录:
sudo mkdir /etc/ansible/host_vars
接下来,在 /etc/ansible/host_vars
目录中使用您的 Ansible 主机 机器的名称创建一个新文件。 在本例中,您将使用 host1
作为主机名:
sudo nano /etc/ansible/host_vars/host1
以下示例配置包括您开始所需的一切,包括:验证方法和服务器地址、接收证书到期提醒的电子邮件地址,以及保存 Let's Encrypt 密钥和证书的目录。
将示例配置复制到文件中:
/etc/ansible/host_vars/host1
--- 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 帐户密钥。
首先,在您选择的新目录中的 Ansible 服务器 上创建一个名为 letsencrypt-issue.yml
的新剧本,例如 /home/user/ansible-playbooks
:
cd ~ mkdir ansible-playbooks cd ansible-playbooks nano letsencrypt-issue.yml
在开始编写 Ansible 任务之前,您需要指定主机和相关设置。 根据您在先决条件教程中引用主机的方式调整以下内容。 然后将以下内容添加到文件顶部:
让加密问题.yml
--- - hosts: "host1" tasks:
现在您可以开始编写所需的任务,首先是创建存储 Let's Encrypt 文件所需的文件系统目录。 在前面内容之后的文件中添加以下 Ansible 任务:
让加密问题.yml
... - 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 任务将在 /etc/letsencrypt
中创建 account
、certs
、csrs
和 keys
目录,这是获取证书将被存储。
您将目录的所有者设置为 root
并应用权限 u=rwx,g=x,o=x
以便只有 root
对它们具有读写权限。 建议这样做,因为目录将包含私钥、 证书签名请求 (CSR) 和签名证书,这些都应该保密。
接下来,需要创建 Let's Encrypt 帐户密钥。 您将使用它来向 Let's Encrypt 服务表明自己的身份。
将以下任务添加到您的剧本中:
让加密问题.yml
... - 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
中工作,因此暂时不要关闭此文件。
您已经创建了剧本并设置了初始配置和任务,以便为获取 Let's Encrypt 证书做准备。 接下来,您将为私钥和 CSR 生成添加更多任务。
第 3 步 — 生成您的私钥和证书签名请求
在此步骤中,您将编写剧本任务以生成所需的私钥和证书签名请求。
本节中的第一个任务将为您的证书生成所需的私钥。 将以下内容添加到您在步骤 2 中开始编写的剧本的末尾:
让加密问题.yml
... - name: "Generate Let's Encrypt private key" shell: "openssl genrsa 4096 | sudo tee /etc/letsencrypt/keys/{{ domain_name }}.key"
同一个域中的子域都将通过使用Subject Alternate Names (SANs)添加到同一个证书中,因此您现在只需要生成一个私钥。
您将使用下一个任务为要获取的证书生成证书签名请求 (CSR)。 这将提交给 Let's Encrypt,以便他们验证和颁发每个证书。
将以下内容添加到剧本的末尾:
让加密问题.yml
... - 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
此任务为您的域生成 CSR,并将 www
子域作为 SAN 添加到证书中。
您将在下一步中继续在 letsencrypt-issue.yml
中工作,因此暂时不要关闭此文件。
您已经编写了 Ansible 任务来为您的证书生成私钥和 CSR。 接下来,您将处理将开始验证和颁发过程的任务。
第 4 步 — 启动 ACME 验证流程
在此步骤中,您将编写一个任务,使用步骤 3 中记录的任务的输出文件将证书签名请求提交给 Let's Encrypt。 这将返回一些 challenge
文件,您需要在您的网络服务器上提供这些文件,以证明您申请证书的域名和子域的所有权。
以下任务将提交 your-domain
的 CSR。 将其添加到剧本的末尾:
让加密问题.yml
... - 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 中配置的变量。 它注册了一个变量,其中包含您将在下一步中使用的 ACME 质询文件。 您需要手动调整变量的名称以包含 your-domain
,但将所有 .
字符替换为 _
,因为点不能用于变量名称. 例如,example.com
的变量将变为 acme_challenge_example_com
。
您将在下一步中继续在 letsencrypt-issue.yml
中工作,因此暂时不要关闭此文件。
您已经编写了一个任务来将您的 CSR 提交给 Let's Encrypt。 接下来,您将添加一个任务来实施 ACME 质询文件,以完成证书验证过程。
第 5 步 — 实施 ACME 挑战文件
在这一步中,您将编写一个 Ansible 任务来读取和实现 ACME 挑战文件。 这些文件证明您有资格为所请求的域和子域获取证书。
ACME 质询文件 必须在您请求证书的域或子域的 /.well-known/acme-challenge/
路径上侦听端口 80
的 Web 服务器上提供。 例如,为了验证 www.your-domain
的证书请求,ACME 质询文件需要可通过 Internet 的以下路径访问:http://www.your-domain/.well-known/acme-challenge
。
根据您当前的 Web 服务器设置,在所需目的地提供这些文件的方法会有很大差异。 但是,在本指南中,我们假设您有一个配置为从 /var/www/html
目录提供文件的 Web 服务器(根据先决条件教程)。 因此,您可能需要相应地调整任务,以便与您自己的 Web 服务器设置兼容。
首先,添加以下任务,创建提供文件所需的 .well-known/acme-challenge/
目录结构到剧本的末尾:
让加密问题.yml
... - 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
以外的目录通过 Web 服务器提供文件,请确保相应地调整路径。
接下来,您将使用以下任务实现在步骤 4 中保存到 acme_challenge_your-domain
变量中的 ACME 挑战文件:
让加密问题.yml
... - 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_
后跟你的域名,但是全是[X195X ] 字符替换为 _
。 此 Ansible 任务将 ACME 验证文件从变量复制到 Web 服务器上的 .well-known/acme-challenge
路径中。 这将允许 Let's Encrypt 检索它们,以验证域的所有权以及您获得证书的资格。
您将在下一步中继续在 letsencrypt-issue.yml
中工作,因此暂时不要关闭此文件。
您已经编写了创建 ACME 验证目录和文件所需的 Ansible 任务。 接下来,您将完成 ACME 验证过程并获取签名证书。
第 6 步 — 获取您的证书
在此步骤中,您将编写一个任务来触发 Let's Encrypt 以验证您提交的 ACME 质询文件,这将允许您获取您的签名证书。
以下任务验证您在步骤 5 中实施的 ACME 质询文件,并将您的签名证书保存到指定路径。 将其添加到剧本的末尾:
让加密问题.yml
... - 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 中配置的变量。 任务完成后,它会将签名的证书保存到指定的路径,允许您开始将其用于您的应用程序或服务。
请注意,您需要手动调整要设置为 ACME 挑战变量名称的任务中的 data
值,类似于步骤 5。
以下是完整的剧本,显示了您添加的每个任务:
让加密问题.yml
- 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 }}"
完成后保存并关闭文件。
您已添加任务以完成 ACME 挑战并获取您的签名证书。 接下来,您将针对您的 Ansible 主机 运行 playbook 以运行所有操作。
第七步——运行你的剧本
现在您已经编写了剧本和所有必需的任务,您可以在您的 Ansible 主机 上运行它来颁发证书。
从您的 Ansible 服务器,您可以使用 ansible-playbook
命令运行 playbook:
ansible-playbook letsencrypt-issue.yml
这将运行剧本,一次一个任务。 您将看到类似于以下内容的输出:
OutputPLAY [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
如果在 playbook 运行时遇到任何错误,将输出这些错误以供您查看。
剧本完成后,您的有效 Let's Encrypt 证书将保存到 主机 上的 /etc/letsencrypt/certs
目录中。 然后,您可以使用它以及 /etc/letsencrypt/keys
中的私钥来保护与您的 Web 服务器、邮件服务器等的连接。
Let's Encrypt 证书默认有效期为 90 天。 您将通过电子邮件向您在步骤 1 中指定的地址收到续订提醒。 要更新您的证书,您可以再次运行 playbook。 确保仔细检查任何使用您的证书的服务是否已获取新证书,因为有时您可能需要手动安装它,将其移动到特定目录,或重新启动服务以使其正确采用新证书。
在此步骤中,您运行了颁发有效 Let's Encrypt 证书的 playbook。
结论
在本文中,您编写了 Ansible playbook 来请求和获取有效的 Let's Encrypt 证书。
下一步,您可以考虑使用新剧本为大量服务器颁发证书。 您甚至可以创建一个中央 ACME 验证服务器,该服务器可以集中颁发证书并将其分发到 Web 服务器。
最后,如果您想了解有关 ACME 规范和 Let's Encrypt 项目的更多信息,您可能希望查看以下链接:
您可能还想查看其他一些相关的 Ansible 教程:
- 如何使用 Ansible:参考指南。
- 如何在 Ubuntu 18.04 上使用 Ansible 自动化服务器设置。
- 如何使用 Vault 保护 Ubuntu 16.04 上的敏感 Ansible 数据。