跳转至

auto-ssl: 在 nginx-module-lua/nginx 中使用 Let's Encrypt 实现即时(免费)SSL 注册和续订

安装

如果您尚未设置 RPM 仓库订阅,请 注册。然后您可以继续以下步骤。

CentOS/RHEL 7 或 Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-auto-ssl

CentOS/RHEL 8+、Fedora Linux、Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-auto-ssl

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-auto-ssl v0.13.1,发布于 2019 年 10 月 01 日。


CI

OpenResty/nginx 中与 Let's Encrypt 实现即时(免费)SSL 注册和续订。

此 OpenResty 插件会在接收到请求时自动且透明地从 Let's Encrypt(一个免费的证书颁发机构)颁发 SSL 证书。其工作流程如下:

  • 收到针对 SNI 主机名的 SSL 请求。
  • 如果系统已经有该域的 SSL 证书,则立即返回(带有 OCSP stapling)。
  • 如果系统尚未拥有该域的 SSL 证书,则从 Let's Encrypt 颁发新的 SSL 证书。域名验证将为您处理。在接收到新证书后(通常在几秒钟内),新证书将被保存、缓存并返回给客户端(而不会中断原始请求)。

这使用了 OpenResty 1.9.7.2+ 中的 ssl_certificate_by_lua 功能。

通过使用 lua-resty-auto-ssl 向 Let's Encrypt 注册 SSL 证书,您同意 Let's Encrypt 订阅协议

创建 /etc/resty-auto-ssl 并确保其可被 nginx 工作进程运行的用户(在此示例中为 "www-data")写入。

$ sudo mkdir /etc/resty-auto-ssl $ sudo chown www-data /etc/resty-auto-ssl

在您的 nginx 配置中实现必要的配置。以下是一个最小示例:

```nginx
events {
  worker_connections 1024;
}

http {
  # "auto_ssl" 共享字典应定义足够的存储空间以容纳您的证书数据。1MB 的存储可以容纳大约 100 个独立域的证书。
  lua_shared_dict auto_ssl 1m;
  # "auto_ssl_settings" 共享字典用于临时存储各种设置,如钩子服务器在 8999 端口使用的密钥。请勿更改或省略它。
  lua_shared_dict auto_ssl_settings 64k;

  # 必须定义 DNS 解析器以使 OCSP stapling 功能正常。
  #
  # 此示例使用 Google 的 DNS 服务器。您可能希望使用系统的默认 DNS 服务器,可以在 /etc/resolv.conf 中找到。如果您的网络不兼容 IPv6,您可能希望通过使用 "ipv6=off" 标志禁用 IPv6 结果(如 "resolver 8.8.8.8 ipv6=off")。
  resolver 8.8.8.8;

  # 初始设置任务。
  init_by_lua_block {
    auto_ssl = (require "resty.auto-ssl").new()

    -- 定义一个函数来确定哪些 SNI 域应自动处理并注册新证书。默认情况下不允许任何域,因此必须进行配置。
    auto_ssl:set("allow_domain", function(domain)
      return true
    end)

    auto_ssl:init()
  }

  init_worker_by_lua_block {
    auto_ssl:init_worker()
  }

  # HTTPS 服务器
  server {
    listen 443 ssl;

    # 动态处理程序,用于为 SNI 域颁发或返回证书。
    ssl_certificate_by_lua_block {
      auto_ssl:ssl_certificate()
    }

    # 您仍然必须为 nginx 启动定义一个静态 ssl_certificate 文件。
    #
    # 您可以使用以下命令生成自签名回退证书:
    #
    # openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
    #   -subj '/CN=sni-support-required-for-valid-ssl' \
    #   -keyout /etc/ssl/resty-auto-ssl-fallback.key \
    #   -out /etc/ssl/resty-auto-ssl-fallback.crt
    ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt;
    ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key;
  }

  # HTTP 服务器
  server {
    listen 80;

    # 用于与 Let's Encrypt 进行域验证的端点。
    location /.well-known/acme-challenge/ {
      content_by_lua_block {
        auto_ssl:challenge_server()
      }
    }
  }

  # 在 8999 端口上运行的内部服务器,用于处理证书任务。
  server {
    listen 127.0.0.1:8999;

    # 增加主体缓冲区大小,以确保内部 POST 始终可以将完整的 POST 内容解析到内存中。
    client_body_buffer_size 128k;
    client_max_body_size 128k;

    location / {
      content_by_lua_block {
        auto_ssl:hook_server()
      }
    }
  }
}

配置

可以在创建的 auto_ssl 实例上设置其他配置选项:

allow_domain

默认值: function(domain, auto_ssl, ssl_options, renewal) return false end

一个函数,用于确定是否应自动为传入域颁发新的 SSL 证书。

默认情况下,resty-auto-ssl 不会执行任何 SSL 注册,直到您定义 allow_domain 函数。您可以返回 true 来处理所有可能的域,但请注意,虚假的 SNI 主机名可能会被用来触发无限数量的 SSL 注册尝试(这些尝试将被拒绝)。更好的方法可能是以某种方式将允许的域列入白名单。

回调函数的参数为:

  • domain: 传入请求的域。
  • auto_ssl: 当前的 auto-ssl 实例。
  • ssl_options: 传递给 ssl_certificate 函数 的可选配置选项表。这可以用于根据每个 nginx server 自定义行为(参见 request_domain 中的示例)。请注意,当此函数用于续订时,此选项 不会 被传递,因此您的函数应相应处理。
  • renewal: 布尔值,指示此函数是否在证书续订期间被调用。当为 true 时,ssl_options 参数将不存在。

使用 Redis 存储适配器时,您可以通过访问 auto_ssl.storage.adapter:get_connection()allow_domain 回调中访问当前 Redis 连接。

示例:

auto_ssl:set("allow_domain", function(domain, auto_ssl, ssl_options, renewal)
  return ngx.re.match(domain, "^(example.com|example.net)$", "ijo")
end)

dir

默认值: /etc/resty-auto-ssl

用于存储配置、临时文件和证书文件(如果使用 file 存储适配器)的基础目录。此目录必须可由 nginx 工作进程运行的用户写入。

示例:

auto_ssl:set("dir", "/some/other/location")

renew_check_interval

默认值: 86400

检查所有域的证书续订的频率(以秒为单位)。默认情况下,每 1 天检查一次。如果证书在 30 天内到期,将自动续订。

示例:

auto_ssl:set("renew_check_interval", 172800)

storage_adapter

默认值: resty.auto-ssl.storage_adapters.file
选项: resty.auto-ssl.storage_adapters.file, resty.auto-ssl.storage_adapters.redis

用于持久存储 SSL 证书的存储机制。提供了基于文件和基于 Redis 的存储适配器,但也可以指定自定义外部适配器(值只需在 lua_package_path 中)。

默认存储适配器将证书持久化到本地文件中。然而,您可能希望考虑另一种存储适配器(如 Redis),原因有几点: - 文件 I/O 会在 OpenResty 中造成阻塞,应避免以获得最佳性能。然而,文件仅在首次看到证书时被读取和写入,然后内容会被缓存到内存中,因此实际的文件 I/O 应该非常有限。 - 如果证书需要在多个服务器之间共享(对于负载均衡环境),本地文件将无法工作。

示例:

auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis")

redis

默认值: { host = "127.0.0.1", port = 6379 }

如果使用 redis 存储适配器,则可以在此表中指定其他连接选项。接受以下选项:

  • host: 要连接的主机(默认为 127.0.0.1)。
  • port: 要连接的端口(默认为 6379)。
  • socket: 可以给出一个 unix 套接字路径,而不是指定 hostport 进行连接(格式为 "unix:/path/to/unix.sock")。
  • connect_options: 传递给 Redis connect 函数的其他连接选项。
  • auth: 传递给 AUTH 命令 的值。
  • db: lua-resty-auto-ssl 用于保存证书的 Redis 数据库编号
  • prefix: 在 Redis 中存储的所有键都以此字符串为前缀。

示例:

auto_ssl:set("redis", {
  host = "10.10.10.1"
})

request_domain

默认值: function(ssl, ssl_options) return ssl.server_name() end

一个函数,用于确定请求的主机名。默认情况下,使用 SNI 域名,但可以实现自定义函数来确定非 SNI 请求的域名(通过基于可以在 SSL 之外确定的内容,如接收请求的端口或 IP 地址)。

回调函数的参数为:

  • ssl: ngx.ssl 模块的实例。
  • ssl_options: 传递给 ssl_certificate 函数 的可选配置选项表。这可以用于根据每个 nginx server 自定义行为。

示例:

此示例及其附带的 nginx server 块将默认使用 SNI 域名,但对于非 SNI 客户端,将根据连接端口响应预定义的主机。连接到 9000 端口将注册并返回 foo.example.com 的证书,而连接到 9001 端口将注册并返回 bar.example.com 的证书。任何其他端口将返回默认的 nginx 回退证书。

auto_ssl:set("request_domain", function(ssl, ssl_options)
  local domain, err = ssl.server_name()
  if (not domain or err) and ssl_options and ssl_options["port"] then
    if ssl_options["port"] == 9000 then
      domain = "foo.example.com"
    elseif ssl_options["port"] == 9001 then
      domain = "bar.example.com"
    end
  end

  return domain, err
end)
server {
  listen 9000 ssl;
  ssl_certificate_by_lua_block {
    auto_ssl:ssl_certificate({ port = 9000 })
  }
}

server {
  listen 9001 ssl;
  ssl_certificate_by_lua_block {
    auto_ssl:ssl_certificate({ port = 9001 })
  }
}

ca

默认值: 默认的 Let's Encrypt CA

要使用的 Let's Encrypt 环境的 URL。通常您不应设置此项,除非您想使用 Let's Encrypt 的 暂存环境

示例:

auto_ssl:set("ca", "https://some-other-letsencrypt.org/directory")

hook_server_port

默认值: 8999

我们内部使用一个特殊服务器在 8999 端口上处理证书任务。可以在此处更改此服务使用的端口。请注意,您还需要在 nginx 配置中更改它。

示例:

auto_ssl:set("hook_server_port", 90)

json_adapter

默认值: resty.auto-ssl.json_adapters.cjson
选项: resty.auto-ssl.json_adapters.cjson, resty.auto-ssl.json_adapters.dkjson

用于编码和解码 JSON 的 JSON 适配器。默认使用 cjson,它与 OpenResty 安装捆绑在一起,通常在大多数情况下应使用。然而,对于 cjson 可能不可用的环境,可以使用基于纯 Lua 的 dkjson 适配器(您需要通过 luarocks 手动安装 dkjson 依赖项以使用此适配器)。

提供了 cjson 和 dkjson JSON 适配器,但也可以指定自定义外部适配器(值只需在 lua_package_path 中)。

示例:

auto_ssl:set("json_adapter", "resty.auto-ssl.json_adapters.dkjson")

http_proxy_options

默认值: nil

配置在进行 OCSP stapling 请求时使用的 HTTP 代理。接受一个选项表,用于 lua-resty-http 的 set_proxy_options

示例:

auto_ssl:set("http_proxy_options", {
  http_proxy = "http://localhost:3128",
})

ssl_certificate 配置

ssl_certificate 函数接受一个可选的配置选项表。这些选项可用于根据每个 nginx server 自定义和控制 SSL 行为。一些内置选项可能会控制 lua-resty-auto-ssl 的默认行为,但也可以将任何其他自定义数据作为选项传递,这些选项将传递给 allow_domainrequest_domain 回调函数。

内置配置选项:

generate_certs

默认值: true

此变量可用于在每个服务器块位置禁用证书生成。

示例:

server {
  listen 8443 ssl;
  ssl_certificate_by_lua_block {
    auto_ssl:ssl_certificate({ generate_certs = false })
  }
}

高级 Let's Encrypt 配置

内部,lua-resty-auto-ssl 使用 dehydrated 作为其 Let's Encrypt 客户端。如果您希望调整更低级的设置,如私钥大小、公钥算法或注册电子邮件,可以在自定义的 dehydrated 配置文件中进行配置。

  • 有关支持选项的完整列表,请参见 dehydrated 的示例配置
  • 自定义 dehydrated 配置文件可以默认放置在 /etc/resty-auto-ssl/letsencrypt/conf.d 目录中(或如果您更改了默认 lua-resty-auto-ssl dir 设置,则调整路径)。

示例 /etc/resty-auto-ssl/letsencrypt/conf.d/custom.sh

KEYSIZE="4096"
KEY_ALGO="rsa"
CONTACT_EMAIL="[email protected]"

注意事项

  • 允许的主机: 默认情况下,resty-auto-ssl 不会执行任何 SSL 注册,直到您定义 allow_domain 函数。您可以返回 true 来处理所有可能的域,但请注意,虚假的 SNI 主机名可能会被用来触发无限数量的 SSL 注册尝试(这些尝试将被拒绝)。更好的方法可能是以某种方式将允许的域列入白名单。
  • 不受信任的代码: 确保安装此模块的 OpenResty 服务器无法执行不受信任的代码。证书和私钥必须可由 Web 服务器用户读取,因此确保这些数据不被泄露非常重要。
  • 文件存储: 默认存储适配器将证书持久化到本地文件中。然而,您可能希望考虑另一种存储适配器(如 Redis),原因有几点:
  • 文件 I/O 会在 OpenResty 中造成阻塞,应避免以获得最佳性能。然而,文件仅在首次看到证书时被读取和写入,然后内容会被缓存到内存中,因此实际的文件 I/O 应该非常有限。
  • 如果证书需要在多个服务器之间共享(对于负载均衡环境),本地文件将无法工作。

开发

在检出代码库后,可以使用 Docker 运行测试套件:

$ docker-compose run --rm app make test

测试可以在 spec 目录中找到,测试套件使用 busted 实现。

发布流程

要向 LuaRocks 发布新版本:

  • 确保 CHANGELOG.md 是最新的。
  • 将 rockspec 文件移动到新版本号(git mv lua-resty-auto-ssl-X.X.X-1.rockspec lua-resty-auto-ssl-X.X.X-1.rockspec),并更新 rockspec 文件中的 versiontag 变量。
  • 提交并标记发布(git tag -a vX.X.X -m "Tagging vX.X.X" && git push origin vX.X.X)。
  • 运行 make release VERSION=X.X.X
  • 将 CHANGELOG 备注复制到 新的 GitHub Release

GitHub

您可以在 nginx-module-auto-ssl 的 GitHub 仓库 中找到此模块的其他配置提示和文档。