acme-dns/acme-dns
GitHub: acme-dns/acme-dns
一个专用于 ACME DNS-01 验证的轻量级 DNS 服务器,通过 RESTful API 实现安全的证书自动化颁发。
Stars: 2659 | Forks: 284
[](https://travis-ci.org/joohoi/acme-dns) [](https://coveralls.io/github/joohoi/acme-dns?branch=master) [](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)。
[](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记录, 信息安全, 域名验证, 提示词优化, 日志审计, 最小权限原则, 测试用例, 程序破解, 自动化证书管理, 自动化运维, 证书颁发机构, 请求拦截, 配置审计