ACME 配置#

Angie 中的 ACME 模块使用 ACME 协议 实现自动证书获取。ACME 协议支持 多种域名验证方法(也称为"验证");本模块实现了 HTTP 验证DNS 验证 以及通过自定义外部服务进行的 基于钩子的验证

配置步骤#

在配置中启用证书请求的常规步骤:

  • 配置 ACME 客户端:在 http 块中使用 acme_client 指令, 指定唯一的客户端名称和其他参数。可以配置多个 ACME 客户端。

  • 指定请求证书的域名:将为所有 server 块中 server_name 指令列出的所有域名颁发单个证书,这些块使用指向同一 ACME 客户端的 acme 指令。

  • 设置请求处理和 ACME 回调:这是验证域名所有权所必需的。设置取决于所选的域名验证方法:

    方法

    用户要求

    多域名

    通配符域名

    HTTP 验证

    在 Angie 服务器上开放端口 80 以接受传入连接。

    DNS 验证

    在 Angie 服务器上开放端口 53(或 acme_dns_port 中指定的端口) 以接受传入连接。

    _acme-challenge. 子域设置 NS 记录, 指向您的 Angie 服务器。

    钩子验证

    创建一个外部服务(脚本或应用程序), 可以根据 Angie 的请求更新 DNS 记录 或通过 Web 服务器提供特殊响应。

  • 使用获取的证书和密钥配置 SSL:该模块将证书和密钥作为 嵌入式变量 提供,可在 配置 中使用以填充 ssl_certificatessl_certificate_key

    有关 SSL 设置说明,请参阅 SSL配置

小技巧

证书获取和续订过程依赖于许多服务,可能需要一些时间。请耐心等待, 如果遇到问题或有疑问,请检查 调试日志

实现细节#

客户端密钥和证书以 PEM 编码 存储在由 --http-acme-client-path 构建选项 指定的目录的子目录中:

$ ls /var/lib/angie/acme/example/

  account.key  certificate.pem  private.key

ACME 客户端需要在 CA 服务器上拥有一个账户。为了创建和管理此账户, 客户端使用私钥(account.key)。如果不存在密钥,则在启动时生成。 然后客户端使用此密钥向服务器注册账户。

备注

如果您已经拥有账户密钥,请在启动前将其放置在客户端的子目录中以重用该账户。 或者,使用 acme_client 中的 account_key 参数指定密钥文件。

ACME 客户端还使用单独的密钥(private.key)用于证书签名请求(CSR)。 如果需要,此证书密钥会在启动时自动创建。

启动时,如果证书不存在,客户端会请求证书,签名并向 CA 服务器发送包含其管理的所有域名的 CSR。 服务器使用 HTTPDNS 验证 验证域名所有权, 并颁发证书,客户端将其保存在本地(certificate.pem)。

如前所述,单个证书涵盖同一 ACME 客户端管理的所有域名,可能产生多域名证书。 证书涵盖的所有名称列表可以在获取的证书的 Subject Alternative Name (SAN)部分中找到。 要从命令行检查:

$ openssl x509 -in certificate.pem -noout -text | grep -A5 "Subject Alternative Name"

当证书即将过期或域名列表发生变化时,客户端会签名并向 CA 服务器发送另一个 CSR。 服务器重新验证所有权并颁发新证书,客户端将其安装在本地,替换之前的证书。

配置 中,获取的证书及其对应的密钥可通过前缀变量 $acme_cert_<名称>$acme_cert_key_<名称> 获得。它们的值是相应文件的内容, 应与 ssl_certificatessl_certificate_key 指令一起使用:

server {

    listen 443 ssl;

    server_name example.com www.example.com;
    acme example;

    ssl_certificate $acme_cert_example;
    ssl_certificate_key $acme_cert_key_example;
}

HTTP 验证#

验证是自动处理的。该过程涉及 ACME 服务器在收到请求后,`通过 HTTP 检索特殊令牌文件 <https://datatracker.ietf.org/doc/html/rfc8555#section-8.3>`_, 从客户端地址 /.well-known/acme-challenge/<TOKEN> 获取。我们的 ACME 模块 会自动跟踪和处理此类请求。当收到包含正确内容的预期响应时,ACME 服务器确认该域名属于客户端。

配置示例#

在此示例中,名为 example 的 ACME 客户端管理 example.comwww.example.com 的证书(注意 HTTP 验证不支持通配符证书):

http {

    resolver 127.0.0.53; # 'acme_client' 指令所需

    acme_client example https://acme-v02.api.letsencrypt.org/directory;

    server {

        listen 80; # 可以位于不同的 'server' 块中
        # 使用不同的域名列表
        # 甚至没有域名列表

        listen 443 ssl;

        server_name example.com www.example.com;
        acme example;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;
    }
}

如前所述,必须开放端口 80 以处理 HTTP ACME 调用。但是, 如本示例所示,此端口的 listen 指令可以放置在单独的 server 块中。 如果不存在包含此指令的现有块,您可以将新块限制为仅处理 ACME 调用:

server {

    listen 80;
    return 444; # 无响应,连接关闭
}

为什么这样可行?该模块在读取标头之后但在选择虚拟服务器和处理 rewritelocation 指令之前拦截对 /.well-known/acme-challenge/<TOKEN> 的请求。 如果 <TOKEN> 值与特定调用的预期值匹配,则处理此类被拦截的请求。 不会发生目录访问;请求完全由模块处理。

DNS 验证#

验证是自动处理的。在处理证书请求时,ACME 服务器会对被验证域名的 _acme-challenge. 子域执行 特殊的 DNS 查询。一旦收到预期的响应,ACME 服务器确认该域名属于客户端。

我们的 ACME 模块会自动跟踪和处理此类请求,前提是您的 DNS 记录已正确配置,将 Angie 服务器指定为 _acme-challenge. 子域的权威名称服务器。

例如,要使用 IP 地址为 93.184.215.14 的 Angie 服务器验证域名 example.com,您的域名 DNS 配置应包含以下记录:

_acme-challenge.example.com. 60    IN      NS       ns.example.com.
             ns.example.com. 60    IN       A       93.184.215.14

此配置将 _acme-challenge.example.com 的 DNS 解析委托给 ns.example.com,并通过将 ns.example.com 映射到 IP 地址(93.184.215.14)来确保其可访问。

警告

NS 记录的传播可能需要几分钟到 48 小时不等,具体取决于 TTL 和 DNS 提供商。建议在请求证书之前验证配置是否正确。

要验证 DNS 是否配置正确,您可以使用以下命令:

$ dig NS _acme-challenge.example.com +short  # 检查 _acme-challenge 子域的 NS 记录

  ns.example.com.

$ dig A ns.example.com +short  # 检查名称服务器的 A 记录

  93.184.215.14

$ nc -zv 93.184.215.14 53  # 检查 DNS 服务器在端口 53 上的可访问性

此方法允许请求通配符证书,例如,在 Subject Alternative Name (SAN)部分包含 *.example.com 条目的证书。要明确请求子域的证书,例如 www.example.com,您必须使用上述方法单独验证该子域。

警告

此场景的适用性在很大程度上取决于您的 DNS 提供商提供的功能;某些提供商不允许此类配置。

配置示例#

总体而言,配置与上一节中的示例类似。不需要 HTTP 特定的设置;只需为 acme_client 指令设置 challenge=dns 即可。

在此示例中,名为 example 的 ACME 客户端管理 example.com*.example.com 的证书:

http {

    resolver 127.0.0.53;

    acme_client example https://acme-v02.api.letsencrypt.org/directory
        challenge=dns;

    server {

        server_name example.com *.example.com;
        acme example;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;
    }
}

基于钩子的验证#

与之前的方法不同,此验证需要额外的工作。ACME 服务器执行标准的 HTTP 验证DNS 验证,但它不是直接与 Angie 服务器交互,而是通过钩子调用(acme_hook)与 Angie 服务器管理的外部服务通信。该服务配置一个单独的 DNS 或 HTTP 服务器,ACME 服务器向其发送请求。

一旦 ACME 服务器从配置的 DNS 或 HTTP 服务器收到预期的响应,它就会确认域名所有权。

当需要通过 ACME 协议更新证书时,Angie 会调用外部服务。调用是通过使用诸如 fastcgi_passscgi_pass 等指令将请求和数据代理到 FastCGI、SCGI 和类似服务器来完成的。

该服务必须返回 2xx 状态码,可以通过 Status 头发送。任何其他代码都被视为错误,证书续订将停止。服务的输出将被忽略。

配置示例#

在此示例中,ACME 客户端 example 配置为使用 DNS 回调进行域名验证,由 acme_client 指令中的 challenge=dns 参数指示。

server 块适用于 example.com 的所有子域(例如 *.example.com),并使用 ACME 客户端 example 来管理证书,如 acme 指令所指定。

配置了一个命名的 location 块来处理 DNS 验证的外部服务调用。acme_hook 指令将服务器链接到 ACME 客户端 example。使用 fastcgi_pass 指令将请求代理到运行在端口 9000 上的本地 FastCGI 服务器。fastcgi_param 指令将有关 ACME 客户端、钩子、质询类型、域名、令牌和密钥授权的数据传递给外部服务。

acme_client example https://acme-v02.api.letsencrypt.org/directory
    challenge=dns;

server {

    listen 80;

    server_name *.example.com;

    acme example;

    ssl_certificate $acme_cert_example;
    ssl_certificate_key $acme_cert_key_example;

    location @acme_hook_location {

        acme_hook example;

        fastcgi_pass localhost:9000;

        fastcgi_param ACME_CLIENT $acme_hook_client;
        fastcgi_param ACME_HOOK $acme_hook_name;
        fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
        fastcgi_param ACME_DOMAIN $acme_hook_domain;
        fastcgi_param ACME_TOKEN $acme_hook_token;
        fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;

        include fastcgi.conf;
    }
}

以下 Perl 脚本演示了相应的外部 FastCGI 服务:

#!/usr/bin/perl

use strict; use warnings;

use FCGI;

my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);

while ($request->Accept() >= 0) {
    print "\r\n";

    my $client =    $ENV{ACME_CLIENT};
    my $hook =      $ENV{ACME_HOOK};
    my $challenge = $ENV{ACME_CHALLENGE};
    my $domain =    $ENV{ACME_DOMAIN};
    my $token =     $ENV{ACME_TOKEN};
    my $keyauth =   $ENV{ACME_KEYAUTH};

    if ($hook eq 'add') {

        DNS_set_TXT_record("_acme-challenge.$domain.", $keyauth);

    } elsif ($hook eq 'remove') {

        DNS_clear_TXT_record("_acme-challenge.$domain.");
    }
};

FCGI::CloseSocket($socket);

这里,:samp:DNS_set_TXT_record()DNS_clear_TXT_record() 是假定在外部 DNS 服务器的配置中添加和删除 TXT 记录的函数,ACME 服务器会查询该 DNS 服务器。这些记录必须包含 Angie 服务器提供的数据,以允许外部 DNS 服务器成功通过验证,类似于 DNS 验证 中描述的过程。此类函数的实现细节超出了本指南的范围;例如,参数也可以通过请求 URI 传递:

# ...

location @acme_hook_location {

    acme_hook example uri=/acme_hook/$acme_hook_name?domain=$acme_hook_domain&key=$acme_hook_keyauth;

    fastcgi_pass localhost:9000;

    fastcgi_param REQUEST_URI $request_uri;
    fastcgi_param ACME_CLIENT $acme_hook_client;
    fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
    fastcgi_param ACME_TOKEN $acme_hook_token;

    include fastcgi.conf;
}

另一个使用 PHP-FPM 的示例:

location @acme_hook_location {

    acme_hook example;
    root /var/www/dns;
    fastcgi_pass unix:/run/php-fpm/php-dns.sock;
    fastcgi_index hook.php;
    fastcgi_param SCRIPT_FILENAME /var/www/dns/hook.php;
    include fastcgi_params;

    fastcgi_param ACME_CLIENT $acme_hook_client;
    fastcgi_param ACME_HOOK $acme_hook_name;
    fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
    fastcgi_param ACME_DOMAIN $acme_hook_domain;
    fastcgi_param ACME_TOKEN $acme_hook_token;
    fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;
}
[dns]
listen = /run/php-fpm/php-dns.sock
listen.mode = 0666
user = angie
group = angie
chdir = /var/www/dns
# ...

传递的参数可以在 PHP 中通过 $_SERVER['...'] 访问。

Stream 模块中的 ACME#

ACME stream 模块可为 TCP 流量启用自动化的 证书签发和使用。 为使其正常工作,您必须首先配置其 HTTP 对应部分: ACME 客户端必须在 http 上下文中声明, 并且 stream 块本身必须放置在配置文件中 http 块*之后*。

配置示例#

默认情况下,使用 HTTP 验证模式来获取证书。 如 HTTP 验证 部分所述, 这需要一个监听 80 端口的 HTTP 服务器:

# HTTP 部分
http {

    resolver 127.0.0.53;

    # 用于 stream 部分的 ACME 客户端
    acme_client example https://acme-v02.api.letsencrypt.org/directory;

    # 用于 HTTP 验证的服务器
    server {

        listen 80;
        return 444;
    }
}

# Stream 部分
stream {

    server {

        listen 12345 ssl;
        proxy_pass backend_upstream;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;

        server_name example.com www.example.com;
        acme example; # 引用 HTTP 部分中定义的 ACME 客户端
    }

    upstream backend_upstream {

        server 127.0.0.1:54321;
    }
}

您也可以使用 DNS 验证, 方法是在 acme_client 指令中配置 challenge=dns; 在这种情况下,将不需要该服务器。

certbot 迁移#

如果您之前在 从 nginx 迁移到 Angie 之前 使用 certbot 从 Let's Encrypt 获取和续订 SSL 证书, 请按照以下步骤过渡到使用我们的 ACME 模块。

假设您按如下方式配置了证书:

$ sudo certbot --nginx -d example.com -d www.example.com

此命令自动创建的配置 通常位于 /etc/nginx/sites-available/example.conf, 看起来类似这样:

server {

    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {

    listen 443 ssl;
    server_name example.com www.example.com;

    root /var/www/example;
    index index.html;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

在上面的示例中,需要修改高亮显示的行。 根据您的情况和偏好,使用 ACME 模块配置 HTTP 验证DNS 验证

最终的 Angie 配置 可能如下所示:

http {

    resolver 127.0.0.53;

    acme_client example https://acme-v02.api.letsencrypt.org/directory;

    server {

        listen 80;
        server_name example.com www.example.com;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl;
        server_name example.com www.example.com;

        root /var/www/example;
        index index.html;

        acme                 example;

        ssl_certificate      $acme_cert_example;
        ssl_certificate_key  $acme_cert_key_example;
    }
}

请记住在 更改后重新加载配置:

$ sudo kill -HUP $(cat /run/angie.pid)

一旦您验证了此配置可以正常工作, 您可以删除 certbot 证书 并禁用或从服务器上完全移除 certbot (如果它不再在其他地方使用), 例如:

$ sudo rm -rf /etc/letsencrypt

$ sudo systemctl stop certbot.timer
$ sudo systemctl disable certbot.timer
$ # -- 或 --
$ sudo rm /etc/cron.d/certbot

$ sudo apt remove certbot
$ # -- 或 --
$ sudo dnf remove certbot