lua-resty-acme
自动 Let's Encrypt 证书服务以及 ACME 协议的 Lua 实现
$ opm get fffonion/lua-resty-acme
lua-resty-acme
自动 Let's Encrypt 证书服务(RSA + ECC)以及 ACMEv2 协议的纯 Lua 实现。
支持 http-01
和 tls-alpn-01
挑战。
描述
此库包含两个部分
resty.acme.autossl
:Let's Encrypt 证书的自动生命周期管理resty.acme.client
:ACME v2 协议的 Lua 实现
使用 opm 安装
opm install fffonion/lua-resty-acme
或者,使用 luarocks 安装
luarocks install lua-resty-acme
# manually install a luafilesystem
luarocks install luafilesystem
注意,使用 LuaRocks 时需要手动安装 luafilesystem
。这样做是为了保持向后兼容性。
此库使用 基于 FFI 的 OpenSSL 后端,目前支持 OpenSSL 1.1.1
、1.1.0
和 1.0.2
系列。
状态
生产环境。
概要
创建账户私钥和回退证书
# create account key
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out /etc/openresty/account.key
# create fallback cert and key
openssl req -newkey rsa:2048 -nodes -keyout /etc/openresty/default.key -x509 -days 365 -out /etc/openresty/default.pem
使用以下示例配置
events {}
http {
resolver 8.8.8.8 ipv6=off;
lua_shared_dict acme 16m;
# required to verify Let's Encrypt API
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
lua_ssl_verify_depth 2;
init_by_lua_block {
require("resty.acme.autossl").init({
-- setting the following to true
-- implies that you read and accepted https://letsencrypt.openssl.ac.cn/repository/
tos_accepted = true,
-- uncomment following for first time setup
-- staging = true,
-- uncomment following to enable RSA + ECC double cert
-- domain_key_types = { 'rsa', 'ecc' },
-- uncomment following to enable tls-alpn-01 challenge
-- enabled_challenge_handlers = { 'http-01', 'tls-alpn-01' },
account_key_path = "/etc/openresty/account.key",
account_email = "youemail@youdomain.com",
domain_whitelist = { "example.com" },
})
}
init_worker_by_lua_block {
require("resty.acme.autossl").init_worker()
}
server {
listen 80;
listen 443 ssl;
server_name example.com;
# fallback certs, make sure to create them before hand
ssl_certificate /etc/openresty/default.pem;
ssl_certificate_key /etc/openresty/default.key;
ssl_certificate_by_lua_block {
require("resty.acme.autossl").ssl_certificate()
}
location /.well-known {
content_by_lua_block {
require("resty.acme.autossl").serve_http_challenge()
}
}
}
}
在测试部署时,建议取消 staging = true
的注释,以便对环境进行端到端测试。这可以避免配置错误导致过多的请求,从而达到 Let's Encrypt API 的 速率限制。
默认情况下,autossl
只创建 RSA 证书。要使用 ECC 证书或两者都使用,请取消 domain_key_types = { 'rsa', 'ecc' }
的注释。请注意,多个证书链仅受 NGINX 1.11.0 或更高版本支持。
在 Nginx 看到带有此 SNI 的请求后,证书将被排队创建,这可能需要几十秒才能完成。在此期间,带有此 SNI 的请求将使用回退证书进行响应。
请注意,必须设置 domain_whitelist
或 domain_whitelist_callback
以包含您希望 autossl 提供服务的域名,以防止使用 SSL 握手中的伪造 SNI 造成潜在的滥用。
domain_whitelist
定义一个表,其中包含所有应包含的域名以及用于为其创建证书的 CN。只允许单个 *
作为通配符。
domain_whitelist = { "domain1.com", "domain2.com", "domain3.com", "*.domain4.com" },
通配符证书
要使此库能够创建通配符证书,必须满足以下要求
通配符域名在
domain_whitelist
中精确显示为*.somedomain.com
。已启用
dns-01
挑战,并且已配置具有与域名匹配的domains
的 DNS 提供程序。
否则,将创建非通配符证书作为回退。
默认情况下,通配符域名 *.example.com
将出现在公用名称中。但是,当 wildcard_domain_in_san
设置为 true
时,将创建具有公用名称 example.com
和主题备用名称 *.example.com
的证书。请注意,*.example.com
和 example.com
都应出现在 dns_provider_accounts
中。
高级用法
使用函数包含域名
domain_whitelist_callback
定义一个函数,该函数接受域名作为参数并返回布尔值以指示是否应包含它。
要匹配域名中的模式,例如 example.com
下的所有子域名,请使用
domain_whitelist_callback = function(domain, is_new_cert_needed)
return ngx.re.match(domain, [[\.example\.com$]], "jo")
end
此外,由于检查域名白名单是在证书阶段运行的。在这里可以使用 cosocket API。请注意,这将增加 SSL 握手的延迟。
domain_whitelist_callback = function(domain, is_new_cert_needed)
-- send HTTP request
local http = require("resty.http")
local res, err = httpc:request_uri("http://example.com")
-- access the storage
local acme = require("resty.acme.autossl")
local value, err = acme.storage:get("key")
-- get cert from resty LRU cache
-- cached = { pkey, cert } or nil if cert is not in cache
local cached, staled, flags = acme.get_cert_from_cache(domain, "rsa")
-- do something to check the domain
-- return is_domain_included
end}),
domain_whitelist_callback
函数提供第二个参数,该参数指示证书是否即将在传入的 HTTP 请求上提供服务(false)或即将请求新证书(true)。这允许在热路径(提供服务请求)上使用缓存的值,同时为新证书从存储中获取新数据。还可以实现不同的逻辑,例如在请求新证书之前进行额外的检查。
定义故障冷却时间
如果证书请求失败,则可能希望阻止 ACME 客户端立即请求另一个证书。默认情况下,冷却时间设置为 300 秒(5 分钟)。可以使用 failure_cooloff
或 failure_cooloff_callback
函数自定义它,例如实现指数退避。
failure_cooloff_callback = function(domain, count)
if count == 1 then
return 600 -- 10 minutes
elseif count == 2 then
return 1800 -- 30 minutes
elseif count == 3 then
return 3600 -- 1 hour
elseif count == 4 then
return 43200 -- 12 hours
elseif count == 5 then
return 43200 -- 12 hours
else
return 86400 -- 24 hours
end
end
tls-alpn-01 挑战
tls-alpn-01 挑战目前在 Openresty 1.15.8.x
、1.17.8.x
和 1.19.3.x
上受支持。
<details> <summary>点击展开示例配置</summary>
events {}
http {
resolver 8.8.8.8 ipv6=off;
lua_shared_dict acme 16m;
# required to verify Let's Encrypt API
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
lua_ssl_verify_depth 2;
init_by_lua_block {
require("resty.acme.autossl").init({
-- setting the following to true
-- implies that you read and accepted https://letsencrypt.openssl.ac.cn/repository/
tos_accepted = true,
-- uncomment following for first time setup
-- staging = true,
-- uncomment folloing to enable RSA + ECC double cert
-- domain_key_types = { 'rsa', 'ecc' },
-- uncomment following to enable tls-alpn-01 challenge
enabled_challenge_handlers = { 'http-01', 'tls-alpn-01' },
account_key_path = "/etc/openresty/account.key",
account_email = "youemail@youdomain.com",
domain_whitelist = { "example.com" },
storage_adapter = "file",
})
}
init_worker_by_lua_block {
require("resty.acme.autossl").init_worker()
}
server {
listen 80;
listen unix:/tmp/nginx-default.sock ssl;
# listen unix:/tmp/nginx-default.sock ssl proxy_protocol;
server_name example.com;
# set_real_ip_from unix:;
# real_ip_header proxy_protocol;
# fallback certs, make sure to create them before hand
ssl_certificate /etc/openresty/default.pem;
ssl_certificate_key /etc/openresty/default.key;
ssl_certificate_by_lua_block {
require("resty.acme.autossl").ssl_certificate()
}
location /.well-known {
content_by_lua_block {
require("resty.acme.autossl").serve_http_challenge()
}
}
}
}
stream {
init_worker_by_lua_block {
require("resty.acme.autossl").init({
-- setting the following to true
-- implies that you read and accepted https://letsencrypt.openssl.ac.cn/repository/
tos_accepted = true,
-- uncomment following for first time setup
-- staging = true,
-- uncomment folloing to enable RSA + ECC double cert
-- domain_key_types = { 'rsa', 'ecc' },
-- uncomment following to enable tls-alpn-01 challenge
enabled_challenge_handlers = { 'http-01', 'tls-alpn-01' },
account_key_path = "/etc/openresty/account.key",
account_email = "youemail@youdomain.com",
domain_whitelist = { "example.com" },
storage_adapter = "file"
})
require("resty.acme.autossl").init_worker()
}
map $ssl_preread_alpn_protocols $backend {
~\bacme-tls/1\b unix:/tmp/nginx-tls-alpn.sock;
default unix:/tmp/nginx-default.sock;
}
server {
listen 443;
listen [::]:443;
ssl_preread on;
proxy_pass $backend;
# proxy_protocol on;
}
server {
listen unix:/tmp/nginx-tls-alpn.sock ssl;
# listen nix:/tmp/nginx-tls-alpn.sock ssl proxy_protocol;
ssl_certificate certs/default.pem;
ssl_certificate_key certs/default.key;
# requires --with-stream_realip_module
# set_real_ip_from unix:;
ssl_certificate_by_lua_block {
require("resty.acme.autossl").serve_tls_alpn_challenge()
}
content_by_lua_block {
ngx.exit(0)
}
}
}
</details>
在上面的示例配置中,我们设置了一个 http 服务器和两个流服务器。
最前面的流服务器侦听 443 端口,并根据客户端 ALPN 将流量路由到不同的上游。tls-alpn-01 响应程序侦听 unix:/tmp/nginx-tls-alpn.sock
。所有正常的 https 流量都侦听 unix:/tmp/nginx-default.sock
。
[stream server unix:/tmp/nginx-tls-alpn.sock ssl]
Y /
[stream server 443] --- ALPN is acme-tls ?
N \
[http server unix:/tmp/nginx-default.sock ssl]
传递给两个子系统中
require("resty.acme.autossl").init
的配置应尽可能保持一致。tls-alpn-01
挑战处理程序不需要任何第三方依赖项。您可以同时启用
http-01
和tls-alpn-01
挑战处理程序。http
和stream
子系统不共享 shm,因此考虑使用除shm
之外的存储。如果必须使用shm
,则需要应用 此补丁。
dns-01 挑战
DNS-01 挑战在 lua-resty-acme > 0.13.0 上受支持。目前,支持以下 DNS 提供程序
cloudflare
:Cloudflaredynv6
:Dynv6dnspod-intl
:Dnspod 国际版(仅支持 Dnspod 令牌,并在密钥字段中使用id,token
)
要了解如何扩展新的 DNS 提供程序以与 dns-01
挑战一起使用,请参阅 "DNS 提供程序"。
使用 dns-01
挑战的示例配置如下
require("resty.acme.autossl").init({
-- setting the following to true
-- implies that you read and accepted https://letsencrypt.openssl.ac.cn/repository/
tos_accepted = true,
-- uncomment following for first time setup
-- staging = true,
-- uncomment following to enable RSA + ECC double cert
-- domain_key_types = { 'rsa', 'ecc' },
-- do not set `http-01` or `tls-alpn-01` if you only plan to use dns-01.
enabled_challenge_handlers = { 'dns-01' },
account_key_path = "/etc/openresty/account.key",
account_email = "youemail@youdomain.com",
domain_whitelist = { "example.com", "subdomain.anotherdomain.com" },
dns_provider_accounts = {
{
name = "cloudflare_prod",
provider = "cloudflare",
secret = "apikey of cloudflare",
domains = { "example.com" },
},
{
name = "dynv6_staging",
provider = "dynv6",
secret = "apikey of dynv6",
domains = { "*.anotherdomain.com" },
},
},
-- uncomment following to create anotherdomain.com in CN and *.anotherdomain.com in SAN
-- wildcard_domain_in_san = true,
})
默认情况下,此库尝试最多 5 分钟进行 DNS 传播。如果 DNS 提供程序的默认 TTL 超过此时间,用户可能需要手动调整 challenge_start_delay
以等待更长时间。
resty.acme.autossl
可以将配置表传递给 resty.acme.autossl.init()
,默认值为
default_config = {
-- accept term of service https://letsencrypt.openssl.ac.cn/repository/
tos_accepted = false,
-- if using the let's encrypt staging API
staging = false,
-- the path to account private key in PEM format
account_key_path = nil,
-- the account email to register
account_email = nil,
-- number of certificate cache, per type
cache_size = 100,
domain_key_paths = {
-- the global domain RSA private key
rsa = nil,
-- the global domain ECC private key
ecc = nil,
},
-- the private key algorithm to use, can be one or both of
-- 'rsa' and 'ecc'
domain_key_types = { 'rsa' },
-- restrict registering new cert only with domain defined in this table
domain_whitelist = nil,
-- restrict registering new cert only with domain checked by this function
domain_whitelist_callback = nil,
-- interval to wait before retrying after failed certificate request
failure_cooloff = 300,
-- function that returns interval to wait before retrying after failed certificate request
failure_cooloff_callback = nil,
-- the threshold to renew a cert before it expires, in seconds
renew_threshold = 7 * 86400,
-- interval to check cert renewal, in seconds
renew_check_interval = 6 * 3600,
-- the store certificates
storage_adapter = "shm",
-- the storage config passed to storage adapter
storage_config = {
shm_name = 'acme',
},
-- the challenge types enabled
enabled_challenge_handlers = { 'http-01' },
-- time to wait before signaling ACME server to validate in seconds
challenge_start_delay = 0,
-- if true, the request to nginx waits until the cert has been generated and it is used right away
blocking = false,
-- if true, the certificate for domain not in whitelist will be deleted from storage
enabled_delete_not_whitelisted_domain = false,
-- the dict of dns providers, each provider should have following struct:
-- {
-- name = "prod_account",
-- provider = "provider_name", -- "cloudflare" or "dynv6"
-- secret = "the api key or token",
-- domains = { "example.com", "*.example.com" }, -- the list of domains that can be used with this provider
-- }
dns_provider_accounts = {},
-- if enabled, wildcard domains like *.example.com will be created as SAN and CN will be example.com
wildcard_domain_in_san = false,
}
如果未指定 account_key_path
,则每次 Nginx 重新加载配置时都会创建一个新的账户密钥。请注意,这可能会触发 Let's Encrypt API 上的新账户 速率限制。
如果未指定 domain_key_paths
,则将为每个证书生成一个新的私钥(4096 位 RSA 和 256 位 prime256v1 ECC)。请注意,生成此类密钥会阻塞工作进程,并且在熵较低的虚拟机上尤其明显。
将配置表直接作为第二个参数传递给 ACME 客户端。以下示例演示了如何使用除 Let's Encrypt 之外的 CA 提供程序以及如何设置首选链。
resty.acme.autossl.init({
tos_accepted = true,
account_email = "example@example.com",
}, {
api_uri = "https://acme.otherca.com/directory",
preferred_chain = "OtherCA PKI Root CA",
}
)
另请参阅下面的 "存储适配器"。
使用分布式存储类型时,提高 challenge_start_delay
的值很有用,以允许存储中的更改传播。当 challenge_start_delay
设置为 0 时,在开始验证挑战之前不会执行任何等待。
autossl.get_certkey
语法:certkey, err = autossl.get_certkey(domain, type?)
从存储中返回 domain
的 PEM 编码证书和私钥。可以选择接受一个 type
参数,该参数可以是 "rsa"
或 "ecc"
;如果省略,则 type
将默认为 "rsa"
。
resty.acme.client
client.new
语法:c, err = client.new(config)
创建一个 ACMEv2 客户端。
config
的默认值为
default_config = {
-- the ACME v2 API endpoint to use
api_uri = "https://acme-v02.api.letsencrypt.org/directory",
-- the account email to register
account_email = nil,
-- the account key in PEM format text
account_key = nil,
-- the account kid (as an URL)
account_kid = nil,
-- external account binding key id
eab_kid = nil,
-- external account binding hmac key, base64url encoded
eab_hmac_key = nil,
-- external account registering handler
eab_handler = nil,
-- storage for challenge
storage_adapter = "shm",
-- the storage config passed to storage adapter
storage_config = {
shm_name = "acme"
},
-- the challenge types enabled, selection of `http-01` and `tls-alpn-01`
enabled_challenge_handlers = {"http-01"},
-- select preferred root CA issuer's Common Name if appliable
preferred_chain = nil,
-- callback function that allows to wait before signaling ACME server to validate
challenge_start_callback = nil,
-- the dict of dns providers, each provider should have following struct:
dns_provider_accounts = {},
}
如果省略了 account_kid
,则用户必须调用 client:new_account()
来注册一个新账户。请注意,当使用相同的 account_key
时,client:new_account()
将返回之前注册的相同 kid
。
如果 CA 需要 "外部账户绑定",则用户可以设置 eab_kid
和 eab_hmac_key
来加载现有账户,或者设置 account_email
和 eab_handler
来注册新账户。eab_hmac_key
必须是 Base64 URL 编码的。在后一种情况下,用户必须调用 client:new_account()
来注册一个新账户。eab_handler
必须是一个函数,该函数接受 account_email
作为参数,并在出现任何错误时返回 eab_kid
、eab_hmac_key
和错误。
eab_handler = function(account_email)
-- do something to register an account with account_email
-- if err then
-- return nil, nil, err
-- end
return eab_kid, eab_hmac_key
end
以下 CA 提供程序的 EAB 处理程序受 lua-resty-acme 支持,用户无需实现自己的 eab_handler
preferred_chain
用于选择在其根 CA 中具有匹配公用名称的链。例如,用户可以使用 "ISRG Root X1"
来强制使用 Let's Encrypt 中的新默认链。当未配置任何值或在任何链中都找不到配置的名称时,将使用默认链。
challenge_start_callback
是一个回调函数,允许客户端在向 ACME 服务器发出信号以开始验证挑战之前等待。这在挑战需要时间才能传播的分布式设置中很有用。challenge_start_callback
接受 challenge_type
和 challenge_token
。客户端每秒调用一次此函数,直到它返回 true
指示挑战应开始;如果未设置此 challenge_start_callback
,则不会执行任何等待。
challenge_start_callback = function(challenge_type, challenge_token)
-- do something here
-- if we are good
return true
end
另请参阅下面的 "存储适配器"。
client:init
语法:err = client:init()
初始化客户端,需要 cosocket API 可用。此函数将登录或注册账户。
client:order_certificate
语法:err = client:order_certificate(domain,...)
使用一个或多个域名创建证书。请注意,通配符域名不受支持,因为它只能通过 dns-01 挑战进行验证。
client:serve_http_challenge
语法:client:serve_http_challenge()
提供 http-01 挑战服务。一个常见的用例是将其作为 /.well-known
路径的 content_by_*
块。
client:serve_tls_alpn_challenge
语法:client:serve_tls_alpn_challenge()
提供 tls-alpn-01 挑战服务。有关如何使用此处理程序的信息,请参阅 本节。
存储适配器
存储适配器用于 autossl
或 acme client
中存储临时或持久数据。根据部署环境,目前有五个可供选择的存储适配器。要实现自定义存储适配器,请参阅 此文档。
文件
基于文件系统的存储。示例配置
storage_config = {
dir = '/etc/openresty/storage',
}
如果省略了 dir
,则将使用操作系统临时目录。
使用 file
存储进行续订时,需要 luafilesystem
或 luafilesystem-ffi
。
共享内存
基于 Lua 共享字典的存储。请注意,此存储在 Nginx 重启(而不是重新加载)之间是易失的。示例配置
storage_config = {
shm_name = 'dict_name',
}
Redis
基于 Redis 的存储。默认配置为
storage_config = {
host = '127.0.0.1',
port = 6379,
database = 0,
-- Redis authentication key
auth = nil,
ssl = false,
ssl_verify = false,
ssl_server_name = nil,
-- namespace as a prefix of key
namespace = "",
}
需要 Redis >= 2.6.0,因为此存储需要 PEXPIRE。
Vault
基于 HashiCorp Vault 的存储。仅支持 KV V2 后端。默认配置为
storage_config = {
host = '127.0.0.1',
port = 8200,
-- secrets kv prefix path
kv_path = "acme",
-- timeout in ms
timeout = 2000,
-- use HTTPS
https = false,
-- turn on tls verification
tls_verify = true
-- SNI used in request, default to host if omitted
tls_server_name = nil,
-- Auth Method, default to token, can be "token" or "kubernetes"
auth_method = "token"
-- Vault token
token = nil,
-- Vault's authentication path to use
auth_path = "kubernetes",
-- The role to try and assign
auth_role = nil,
-- The path to the JWT
jwt_path = "/var/run/secrets/kubernetes.io/serviceaccount/token",
-- Vault namespace
namespace = nil,
}
支持不同的身份验证方法
令牌:这是默认值,允许在配置中传递文字“令牌”
Kubernetes:通过此方法,可以使用 Vault 为 Kubernetes 构建的身份验证方法。这基本上是获取服务账户令牌并验证它是否已由 Kubernetes CA 签名。这里的主要好处是,配置文件不再公开您的令牌。
以下配置在此处适用:
`lua -- 要使用的 Vault 身份验证路径 auth_path = "kubernetes", -- 要尝试分配的角色 auth_role = nil, -- JWT 的路径 jwt_path = "/var/run/secrets/kubernetes.io/serviceaccount/token",
`
Consul
基于 HashiCorp Consul 的存储。默认配置为
storage_config = {
host = '127.0.0.1',
port = 8500,
-- kv prefix path
kv_path = "acme",
-- Consul ACL token
token = nil,
-- timeout in ms
timeout = 2000,
}
etcd
基于 etcd 的存储。目前仅支持 v2
协议。默认配置为
storage_config = {
http_host = 'http://127.0.0.1:4001',
protocol = 'v2',
key_prefix = '',
timeout = 60,
ssl_verify = false,
}
Etcd 存储需要安装 lua-resty-etcd 库。可以使用 opm install api7/lua-resty-etcd
或 luarocks install lua-resty-etcd
手动安装。
DNS 提供商
要创建自定义 DNS 提供商,请按照以下步骤操作
在
lib/resty/acme/dns_provider
下创建类似route53.lua
的文件实现以下函数签名
function _M.new(token)
-- ...
return self
end
function _M:post_txt_record(fqdn, content)
return ok, err
end
function _M:delete_txt_record(fqdn)
return ok, err
end
其中 token
是 apikey,fqdn
是要设置记录的 DNS 记录名称,content
是记录的值。
待办事项
autossl: ocsp staping
测试
通过运行 bash t/fixtures/prepare_env.sh
设置端到端测试环境。
然后运行 cpanm install Test::Nginx::Socket
,然后运行 prove -r t
。
致谢
file
存储的改进由 @dbalagansky 完成'vault' 存储中添加 Kubernetes 身份验证由 @UXabre 完成
dns-01
挑战的初始支持由 @yuweizzz 完成
版权和许可
此模块根据 BSD 许可证授权。
版权所有 (C) 2019,作者 fffonion <fffonion@gmail.com>。
保留所有权利。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否修改。
源代码的重新分发必须保留上述版权声明、此条件列表和以下免责声明。
二进制形式的重新分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。
本软件由版权所有者和贡献者“按原样”提供,并且不提供任何明示或暗示的保证,包括但不限于适销性和特定用途适用性的暗示保证。在任何情况下,版权所有者或贡献者均不对任何直接、间接、附带、特殊、惩戒性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润损失;或业务中断)负责,无论这些损害是如何造成的以及基于任何责任理论,无论是合同、严格责任还是侵权行为(包括疏忽或其他),即使已被告知此类损害的可能性。
另请参阅
haproxytech/haproxy-lua-acme HAProxy 中使用的 ACME Lua 实现。
作者
fffonion
许可证
3bsd
依赖项
ledgetech/lua-resty-http >= 0.12, openresty/lua-resty-lrucache >= 0.08, fffonion/lua-resty-openssl >= 0.7.0, spacewander/luafilesystem >= 0.1, luajit
版本
-
fffonion/lua-resty-acme 0.14.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2024-05-28 14:53:43
-
fffonion/lua-resty-acme 0.12.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2023-09-05 07:23:16
-
fffonion/lua-resty-acme 0.11.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2023-03-24 07:28:12
-
fffonion/lua-resty-acme 0.10.1自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2022-12-06 10:00:28
-
fffonion/lua-resty-acme 0.10.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2022-11-18 09:33:32
-
fffonion/lua-resty-acme 0.9.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2022-10-27 04:24:26
-
fffonion/lua-resty-acme 0.8.2自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2022-10-20 08:30:53
-
fffonion/lua-resty-acme 0.8.1自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2022-07-29 13:39:48
-
fffonion/lua-resty-acme 0.8.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2022-04-08 11:35:01
-
fffonion/lua-resty-acme 0.7.2自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2021-09-17 22:16:38
-
fffonion/lua-resty-acme 0.7.1自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2021-07-22 13:53:07
-
fffonion/lua-resty-acme 0.6.1自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2021-06-25 14:20:29
-
fffonion/lua-resty-acme 0.6.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2021-02-19 11:29:00
-
fffonion/lua-resty-acme 0.5.11自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2021-01-05 09:18:13
-
fffonion/lua-resty-acme 0.5.10自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-12-07 16:41:34
-
fffonion/lua-resty-acme 0.5.9自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-11-26 08:53:42
-
fffonion/lua-resty-acme 0.5.8自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-09-10 15:53:09
-
fffonion/lua-resty-acme 0.5.7自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-08-31 08:10:29
-
fffonion/lua-resty-acme 0.5.6自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-08-11 17:07:30
-
fffonion/lua-resty-acme 0.5.5自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-06-29 13:15:44
-
fffonion/lua-resty-acme 0.5.4自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-06-24 09:49:32
-
fffonion/lua-resty-acme 0.5.3自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-05-18 14:19:32
-
fffonion/lua-resty-acme 0.5.1自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-04-24 16:46:42
-
fffonion/lua-resty-acme 0.5.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2020-02-17 12:17:38
-
fffonion/lua-resty-acme 0.4.3自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-12-25 23:38:52
-
fffonion/lua-resty-acme 0.4.2自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-12-18 00:02:43
-
fffonion/lua-resty-acme 0.4.1自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-12-12 00:04:02
-
fffonion/lua-resty-acme 0.3.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-11-13 07:37:20
-
fffonion/lua-resty-acme 0.2.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-10-23 03:28:52
-
fffonion/lua-resty-acme 0.1.4自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-10-22 20:36:22
-
fffonion/lua-resty-acme 0.1.3自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-10-18 18:51:43
-
fffonion/lua-resty-acme 0.1.2自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-09-26 05:36:22
-
fffonion/lua-resty-acme 0.1.1自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-09-20 23:32:35
-
fffonion/lua-resty-acme 0.1.0自动 Let's Encrypt 证书服务和 ACME 协议的 Lua 实现 2019-09-20 22:08:24