acme-dns/acme-dns

GitHub: acme-dns/acme-dns

一个专用于 ACME DNS-01 验证的轻量级 DNS 服务器,通过 RESTful API 实现安全的证书自动化颁发。

Stars: 2659 | Forks: 284

[![Build Status](https://travis-ci.org/joohoi/acme-dns.svg?branch=master)](https://travis-ci.org/joohoi/acme-dns) [![Coverage Status](https://coveralls.io/repos/github/joohoi/acme-dns/badge.svg?branch=master)](https://coveralls.io/github/joohoi/acme-dns?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/joohoi/acme-dns)](https://goreportcard.com/report/github.com/joohoi/acme-dns) # acme-dns 一个简化的 DNS 服务器,带有 RESTful HTTP API,旨在提供一种简单的方法来自动化 ACME DNS 验证。 ## 为什么? 许多 DNS 服务器不提供 API 来支持 ACME DNS 验证的自动化。而那些提供 API 的服务器,往往赋予密钥过大的权限。 为了实现有意义的流程自动化,往往需要将密钥随意留存在各种服务器上,这通常是安全隐患。 Acme-dns 提供了一个专用于 TXT 记录更新的简单 API,应与 ACME 魔术 "_acme-challenge" - 子域 CNAME 记录配合使用。这样,即使不幸泄露了 API 密钥,其影响也仅限于相关的子域 TXT 记录。 所以归根结底,这关乎 **易用性** 和 **安全性**。 关于潜在问题的更详细解释以及其他提议的解决方案,请参阅 EFF deeplinks 博客关于此主题的文章:https://www.eff.org/deeplinks/2018/02/technical-deep-dive-securing-automation-acme-dns-challenge-validation ## 功能特性 - 简化的 DNS 服务器,用于响应你的 ACME DNS 验证 (TXT) - 自定义记录(支持提供所需的 A, AAAA, NS 等记录) - HTTP API 自动获取并使用 Let's Encrypt TLS 证书 - 限制 /update API 端点的访问范围到特定 CIDR 掩码,可在 /register 请求中定义 - 支持 SQLite 和 PostgreSQL 作为数据库后端 - 滚动更新两条 TXT 记录,以便能够同时响应针对 `yourdomain.tld` 和 `*.yourdomain.tld` 证书的验证,因为这两个验证都指向同一个子域。 - 简单的部署(毕竟是 Go 嘛) ## 用法 支持 Certbot 认证钩子的 acme-dns 客户端应用程序可在以下地址找到:[https://github.com/acme-dns/acme-dns-client](https://github.com/acme-dns/acme-dns-client)。 [![asciicast](https://asciinema.org/a/94903.png)](https://asciinema.org/a/94903) 使用 acme-dns 是一个三步过程(假设你已经搭建了自托管服务器): - 获取凭据和唯一子域(通过简单的 POST 请求,例如发送到 https://auth.acme-dns.io/register) - 在你现有的区域中创建一个(ACME 魔术)CNAME 记录,指向注册时获得的子域。(例如:`_acme-challenge.domainiwantcertfor.tld. CNAME a097455b-52cc-4569-90c8-7a4b97c6eba8.auth.example.org` ) - 使用你的凭据向 acme-dns 服务器 POST 新的 DNS 验证值,以便 CA 进行验证。 - 设置 Crontab 然后就不用管了。 ## API ### 注册端点 该方法返回一个新的唯一子域和更新记录所需的凭据。 Fulldomain 是你可以将自己的 `_acme-challenge` 子域 CNAME 记录指向的目标地址。 使用这些凭据,你可以更新服务中的 TXT 响应内容,使其与证书颁发机构 (CA) 发出的验证令牌(后文称为 \_\_\_validation\_token\_received\_from\_the\_ca\_\_\_)相匹配。 **可选:** 你可以 POST JSON 数据,使用 CIDR 表示法将 `/update` 请求限制在预定义的源网络内。 ```POST /register``` #### 可选输入示例 ``` { "allowfrom": [ "192.168.100.1/24", "1.2.3.4/32", "2002:c0a8:2a00::0/40" ] } ``` ```Status: 201 Created``` ``` { "allowfrom": [ "192.168.100.1/24", "1.2.3.4/32", "2002:c0a8:2a00::0/40" ], "fulldomain": "8e5700ea-a4bf-41c7-8a77-e990661dcc6a.auth.acme-dns.io", "password": "htB9mR9DYgcu9bX_afHF62erXaH2TS7bg9KW3F7Z", "subdomain": "8e5700ea-a4bf-41c7-8a77-e990661dcc6a", "username": "c36f50e8-4632-44f0-83fe-e070fef28a10" } ``` ### 更新端点 该方法允许你更新唯一子域的 TXT 回答内容。通常由自动化 ACME 客户端自动执行。 ```POST /update``` #### 必需的请求头 | Header name | Description | Example | | ------------- |--------------------------------------------|-------------------------------------------------------| | X-Api-User | 注册时收到的 UUIDv4 用户名 | `X-Api-User: c36f50e8-4632-44f0-83fe-e070fef28a10` | | X-Api-Key | 注册时收到的密码 | `X-Api-Key: htB9mR9DYgcu9bX_afHF62erXaH2TS7bg9KW3F7Z` | #### 输入示例 ``` { "subdomain": "8e5700ea-a4bf-41c7-8a77-e990661dcc6a", "txt": "___validation_token_received_from_the_ca___" } ``` #### 响应 ```Status: 200 OK``` ``` { "txt": "___validation_token_received_from_the_ca___" } ``` ### 健康检查端点 该方法可用于检查服务器的就绪度和/或存活状态。成功时将返回状态码 200,否则将无法访问。 ```GET /health``` ## 自托管 建议你运行自己的 acme-dns 实例,因为你实际上是在授权 acme-dns 服务器代表你向发起验证的 CA 提供答案,这使得该实例能够为指向它的 CNAME 所对应的域请求(并获取)TLS 证书。 有关如何执行此操作,请参阅安装部分。 ## 安装说明 1. 安装 [Go 1.13 或更新版本](https://golang.org/doc/install)。 2. 构建 acme-dns: ``` git clone https://github.com/joohoi/acme-dns cd acme-dns export GOPATH=/tmp/acme-dns go build ``` 3. 将构建好的 acme-dns 二进制文件移动到 $PATH 中的目录,例如: `sudo mv acme-dns /usr/local/bin` 4. 编辑 config.cfg 以满足你的需求(参见 [配置](#configuration)))。`acme-dns` 将从 `/etc/acme-dns/config.cfg` 或 `./config.cfg` 读取配置文件,或者使用 `-c` 标志指定的位置。 5. 如果你的系统有 systemd,你可以选择将 acme-dns 安装为服务,以便它在启动时运行并由 systemd 跟踪。这也允许我们添加 `CAP_NET_BIND_SERVICE` 功能,以便 acme-dns 可以由非 root 用户运行。 1. 确保你已将配置文件移动到 `/etc/acme-dns/config.cfg`,以便 acme-dns 可以全局访问它。 2. 将 acme-dns 可执行文件从 `~/go/bin/acme-dns` 移动到 `/usr/local/bin/acme-dns`(任何位置都可以,只需确保更改 `acme-dns.service` 以匹配)。 3. 创建一个最小化的 acme-dns 用户:`sudo adduser --system --gecos "acme-dns Service" --disabled-password --group --home /var/lib/acme-dns acme-dns`。 4. 将 systemd 服务单元从 `acme-dns.service` 移动到 `/etc/systemd/system/acme-dns.service`。 5. 重新加载 systemd 单元:`sudo systemctl daemon-reload`。 6. 设置 acme-dns 开机自启:`sudo systemctl enable acme-dns.service`。 7. 运行 acme-dns:`sudo systemctl start acme-dns.service`。 6. 如果你没有安装 systemd 服务,请运行 `acme-dns`。请注意,acme-dns 需要打开特权端口(53,domain),因此需要以提升的权限运行。 ### 使用 Docker 1. 拉取最新的 acme-dns Docker 镜像:`docker pull joohoi/acme-dns`。 2. 创建目录:`config` 用于存放配置文件,`data` 用于存放 sqlite3 数据库。 3. 将 [配置模板](https://raw.githubusercontent.com/joohoi/acme-dns/master/config.cfg) 复制到 `config/config.cfg`。 4. 修改 `config.cfg` 以满足你的需求。 5. 运行 Docker,此示例假设你在 `config.cfg` 中设置了 `port = "80"`: ``` docker run --rm --name acmedns \ -p 53:53 \ -p 53:53/udp \ -p 80:80 \ -v /path/to/your/config:/etc/acme-dns:ro \ -v /path/to/your/data:/var/lib/acme-dns \ -d joohoi/acme-dns ``` ### Docker Compose 1. 创建目录:`config` 用于存放配置文件,`data` 用于存放 sqlite3 数据库。 2. 将 [配置模板](https://raw.githubusercontent.com/joohoi/acme-dns/master/config.cfg) 复制到 `config/config.cfg`。 3. 复制 [项目中的 docker-compose.yml](https://raw.githubusercontent.com/joohoi/acme-dns/master/docker-compose.yml),或者创建你自己的。 4. 编辑 `config/config.cfg` 和 `docker-compose.yml` 以满足你的需求,然后运行 `docker-compose up -d`。 ## DNS 记录 注意:在本文档中: - `auth.example.org` 是 acme-dns 服务器的主机名 - acme-dns 将提供 `*.auth.example.org` 记录的解析 - `198.51.100.1` 是运行 acme-dns 的系统的 **公网** IP 地址 这些值应根据你的环境进行更改。 你需要在你域名的常规 DNS 服务器上添加一些 DNS 记录: - 指向 `auth.example.org` 的 `auth.example.org` 的 `NS` 记录(这意味着 `auth.example.org` 负责任何 `*.auth.example.org` 记录) - 指向 `198.51.100.1` 的 `auth.example.org` 的 `A` 记录 - 如果使用 IPv6,需添加指向 IPv6 地址的 `AAAA` 记录。 - 你将进行身份验证的每个域都需要添加一个 `_acme-challenge` `CNAME` 子域。你使用的 [客户端](README.md#clients) 将解释如何执行此操作。 ## 测试 你可能希望在进行实际查询之前测试 acme-dns 是否正常工作。 1. 确认 acme-dns 子域的 DNS 查找按预期工作:`dig auth.example.org`。 2. 调用 `/register` API 端点以注册一个测试域: ``` $ curl -X POST https://auth.example.org/register {"username":"eabcdb41-d89f-4580-826f-3e62e9755ef2","password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0","fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org","subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf","allowfrom":[]} ``` 3. 调用 `/update` API 端点以设置测试 TXT 记录。传递上面执行的 `register` 调用收到的 `username`、`password` 和 `subdomain`: ``` $ curl -X POST \ -H "X-Api-User: eabcdb41-d89f-4580-826f-3e62e9755ef2" \ -H "X-Api-Key: pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0" \ -d '{"subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", "txt": "___validation_token_received_from_the_ca___"}' \ https://auth.example.org/update ``` 注意:`txt` 字段的长度必须正好为 43 个字符,否则 acme-dns 将拒绝它 4. 对测试子域执行 DNS 查找,以确认正在提供更新的 TXT 记录: ``` $ dig -t txt @auth.example.org d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org ``` ## 配置 ``` [general] # DNS 接口。请注意 systemd-resolved 可能会占用 127.0.0.53 上的 53 端口 # 在这种情况下 acme-dns 会报错,您需要定义监听接口 # 例如:listen = "127.0.0.1:53" listen = "127.0.0.1:53" # 协议,可选 "both", "both4", "both6", "udp", "udp4", "udp6" 或 "tcp", "tcp4", "tcp6" protocol = "both" # 用于响应请求的域名 domain = "auth.example.org" # zone 名称服务器 nsname = "auth.example.org" # 管理员电子邮件地址,其中 @ 被替换为 . nsadmin = "admin.example.org" # 除了 TXT 之外提供的预定义记录 records = [ # domain pointing to the public IP of your acme-dns server "auth.example.org. A 198.51.100.1", # specify that auth.example.org will resolve any *.auth.example.org records "auth.example.org. NS auth.example.org.", ] # 来自 CORS 等的调试消息 debug = false [database] # 要使用的数据库引擎,sqlite3 或 postgres engine = "sqlite3" # 连接字符串,sqlite3 使用文件名,postgres 使用 postgres://$username:$password@$host/$db_name # 请注意,默认 Docker 镜像对 sqlite3 使用路径 /var/lib/acme-dns/acme-dns.db connection = "/var/lib/acme-dns/acme-dns.db" # connection = "postgres://user:password@localhost/acmedns_db" [api] # 监听 IP,例如 127.0.0.1 ip = "0.0.0.0" # 禁用注册端点 disable_registration = false # 监听端口,例如默认 HTTPS 的 443 port = "443" # 可选值:"letsencrypt", "letsencryptstaging", "cert", "none" tls = "letsencryptstaging" # 仅在 tls = "cert" 时使用 tls_cert_privkey = "/etc/tls/example.org/privkey.pem" tls_cert_fullchain = "/etc/tls/example.org/fullchain.pem" # 仅在 tls = "letsencrypt" 时使用 acme_cache_dir = "api-certs" # Let's Encrypt 将向其发送 API 证书过期通知的可选电子邮件地址 notification_email = "" # CORS AllowOrigins,可使用通配符 corsorigins = [ "*" ] # 使用 HTTP 头获取客户端 IP use_header = false # 用于提取 IP 地址 / IP 地址列表的头名称 header_name = "X-Forwarded-For" [logconfig] # 日志级别:"error", "warning", "info" 或 "debug" loglevel = "debug" # 可选值:stdout, TODO file & integrations logtype = "stdout" # 日志文件的文件路径 TODO # logfile = "./acme-dns.log" # 格式,"json" 或 "text" logformat = "text" ``` ## HTTPS API RESTful acme-dns API 可以通过两种方式通过 HTTPS 公开: 1. 使用 `tls = "letsencrypt"` 并让 acme-dns 使用 Let's Encrypt 自动颁发自己的证书。 2. 使用 `tls = "cert"` 并通过 `tls_cert_fullchain` 和 `tls_cert_privkey` 提供你自己的 HTTPS 证书链和私钥。 在可能的情况下,推荐使用第一种方法。这是让 acme-dns 通过 HTTPS 公开其 API 最简单、最安全的方法。 **警告**:如果你选择使用 `tls = "cert"`,你必须注意证书 *不要过期*!如果证书过期,并且你用于颁发证书的 ACME 客户端依赖于 ACME DNS API 来更新 TXT 记录,你将陷入困境:API 证书已过期,但无法续期,因为 ACME 客户端将拒绝连接到续期所需的 ACME DNS API。 ## 客户端 - acme.sh: [https://github.com/Neilpang/acme.sh](https://github.com/Neilpang/acme.sh) - Certify The Web: [https://github.com/webprofusion/certify](https://github.com/webprofusion/certify) - cert-manager: [https://github.com/jetstack/cert-manager](https://github.com/jetstack/cert-manager) - Lego: [https://github.com/xenolf/lego](https://github.com/xenolf/lego) - Posh-ACME: [https://github.com/rmbolger/Posh-ACME](https://github.com/rmbolger/Posh-ACME) - Sewer: [https://github.com/komuw/sewer](https://github.com/komuw/sewer) - Traefik: [https://github.com/containous/traefik](https://github.com/containous/traefik) - Windows ACME Simple (WACS): [https://www.win-acme.com](https://www.win-acme.com) ### 认证钩子 - 带 Certbot 认证钩子的 acme-dns-client: [https://github.com/acme-dns/acme-dns-client](https://github.com/acme-dns/acme-dns-client) - Python 编写的 Certbot 认证钩子: [https://github.com/joohoi/acme-dns-certbot-joohoi](https://github.com/joohoi/acme-dns-certbot-joohoi) - Go 编写的 Certbot 认证钩子: [https://github.com/koesie10/acme-dns-certbot-hook](https://github.com/koesie10/acme-dns-certbot-hook) ### 库 - Python 通用客户端库 ([PyPI](https://pypi.python.org/pypi/pyacmedns/)): [https://github.com/joohoi/pyacmedns](https://github.com/joohoi/pyacmedns) - Go 通用客户端库: [https://github.com/cpu/goacmedns](https://github.com/cpu/goacmedns) ## 待办事项 - 记录到文件 - DNSSEC - 想看到某些功能实现,请提交功能请求! ## 贡献 acme-dns 欢迎贡献。 如果你有改进的想法,请提出新问题或随时编写 PR! ## 许可证 acme-dns 在 [MIT 许可证](https://www.opensource.org/licenses/MIT) 下发布。
标签:ACME, CNAME记录, DNS挑战验证, DNS服务器, EVTX分析, EVTX分析, Go语言, HTTP API, Let's Encrypt, meg, PostgreSQL, RESTful API, SQLite, SSL/TLS证书, TXT记录, 信息安全, 域名验证, 提示词优化, 日志审计, 最小权限原则, 测试用例, 程序破解, 自动化证书管理, 自动化运维, 证书颁发机构, 请求拦截, 配置审计