用Hashicorp Vault搭建自己的CA(四上)——ACME

阅读本篇文章需要先完成上一篇:古法手作

在上一篇文章中,我们用古法手作的方式从CA取得证书并安装到web服务器,能用是能用,但是这证书的整个生命周期管理全是纯手动,既麻烦又容易出错。这篇文章我们将使用ACME协议来自动化,做到证书自动申请、续期、安装。

C ACME协议:

自动证书管理环境通常包括两部分:

一部分是ACME服务端,由证书颁发机构控制,比如我们熟知的Let’s Encryptzerossl就是ACME服务端,不过他们签发的是公共证书;在本系列文章里,Vault是ACME服务端。

另一部分是ACME客户端,一般由web服务器/负载均衡器或独立程序控制,比如Caddy、Certbot、acme.sh等。 ACME挑战过程简易示意图 ACME客户端会发送证书请求到ACME服务端,其中包含ACME客户端要用的“common name”,一般也是web服务器监听的FQDN。而ACME服务器则须验证ACME客户端是否对相应域名有控制权。它会向ACME客户端发起“挑战”(challenge)。“挑战”的方法最常用的是http01,即ACME服务器告诉ACME客户端一些随机内容,要求对方将此内容放置于web服务器内;然后ACME服务器会启动web客户端去ACME客户端的web服务器访问,能访问到刚才的内容即可确定“挑战”成功。对于不能开放TCP 80端口的ACME客户端,“挑战”也可以使用DNS解析的方法完成。

D ACME服务端

先看看是否开启了ACME服务:

1
vault pki health-check ca-int-x1/

显示AIA信息:

1
vault read ca-int-x1/config/cluster

以上要是发现没有配置过AIA路径的就得:

1
2
3
vault write ca-int-x1/config/cluster \
   path=$VAULT_ADDR/v1/ca-int-x1 \
   aia_path=$VAULT_ADDR/v1/ca-int-x1

ℹ️ ACME客户端须信任Vault服务捆绑的数字证书(假如是自签名),详情见上一篇文章

调整中间CA的参数:

1
2
3
4
5
6
7
vault secrets tune \
      -passthrough-request-headers=If-Modified-Since \
      -allowed-response-headers=Last-Modified \
      -allowed-response-headers=Location \
      -allowed-response-headers=Replay-Nonce \
      -allowed-response-headers=Link \
      ca-int-x1

开启ACME服务端:

1
vault write ca-int-x1/config/acme enabled=true

E ACME客户端

ACME客户端有好几家呢,看过前文的老读者朋友们都知道我最喜欢Caddy了。它作为http服务器同时也有ACME客户端的能力。

Caddy

Caddy集成了ACME客户端功能,在本系列第一篇文章中我们就用这个功能来获取免费的公共证书,但是现在我们用它来与我们自己的ACME服务端交互。

这次我使用一台运行Debian的Linux机器,运行Caddy,它的FQDN是web-dev-02.lab.miyunda.com,还是先准备一个网页文件夹

1
2
sudo mkdir -p /caddyroot/web-dev-02
sudo nano /caddyroot/web-dev-02/index.html

其内容为:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
    <head>
        <title>Hello nerd</title>
    </head>
    <body>
        <p>我是web-dev-02</p>
    </body>
</html>

修改Caddy的配置文件

1
sudo nano /etc/caddy/Caddyfile

比起上一篇文章,在配置文件中,我们不再提供证书在文件系统中的路径,而是告诉Caddy去找ACME服务器:

1
2
3
4
5
6
7
{
        acme_ca  https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory
}
web-dev-02.lab.miyunda.com {
        root * /caddyroot/web-dev-02
        file_server
}

令新配置生效:

1
sudo systemctl reload caddy # 或者 sudo systemctl start caddy

查看日志文件:

1
sudo journalctl -xeu caddy | less

日志文件里面有值得我们关注的信息,下面展示了ACME服务“挑战”Caddy,方式为“http-01”,也就是ACME服务器访问了Caddy临时运行的web服务,以此确定了Caddy对这个FQDN web-dev-02.lab.miyunda.com的使用权。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Jun 13 21:22:46 web-dev-02 systemd[1]: Started caddy.service - Caddy.
░░ Subject: A start job for unit caddy.service has finished successfully
░░ Defined-By: systemd
░░ Support: https://www.debian.org/support
░░
░░ A start job for unit caddy.service hasfinished successfully.
░░
░░ The job identifier is 938.
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.093515,"msg":"serving initial configuration"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.0936847,"logger":"tls.obtain","msg":"acquiring lock","identifier":"web-dev-02.lab.miyunda.com"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.094313,"logger":"tls.obtain","msg":"lock acquired","identifier":"web-dev-02.lab.miyunda.com"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.0947223,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"web-dev-02.lab.miyunda.com"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.095472,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0004fdea0"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.0956464,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/var/lib/caddy/.local/share/caddy"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.0957217,"logger":"tls","msg":"finished cleaning storage units"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.1640933,"logger":"http","msg":"waiting on internal rate limiter","identifiers":["web-dev-02.lab.miyunda.com"],"ca":"https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory","account":""}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.1642356,"logger":"http","msg":"done waiting on internal rate limiter","identifiers":["web-dev-02.lab.miyunda.com"],"ca":"https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory","account":""}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.1676006,"logger":"http.acme_client","msg":"trying to solve challenge","identifier":"web-dev-02.lab.miyunda.com","challenge_type":"http-01","ca":"https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.1703296,"logger":"http","msg":"served key authentication","identifier":"web-dev-02.lab.miyunda.com","challenge":"http-01","remote":"192.168.3.130:56076","distributed":false}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.4230063,"logger":"http.acme_client","msg":"validations succeeded; finalizing order","order":"https://vault-dev.miyunda.com/v1/ca-int-x1/acme/order/a3a5762c-7d6e-7ca9-0690-e571344156d5"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.4378805,"logger":"http.acme_client","msg":"successfully downloaded available certificate chains","count":1,"first_url":"https://vault-dev.miyunda.com/v1/ca-int-x1/acme/order/a3a5762c-7d6e-7ca9-0690-e571344156d5/cert"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.4382524,"logger":"tls.obtain","msg":"certificate obtained successfully","identifier":"web-dev-02.lab.miyunda.com"}
Jun 13 21:22:46 web-dev-02 caddy[4573]: {"level":"info","ts":1718284966.4383457,"logger":"tls.obtain","msg":"releasing lock","identifier":"web-dev-02.lab.miyunda.com"}

可以用浏览器访问web服务器看看,也可以用命令行试试:

1
echo | openssl s_client -showcerts -servername web-dev-02.lab.miyunda.com -connect web-dev-02.lab.miyunda.com:443 2>/dev/null | openssl x509 -inform pem -noout -text

acme.sh

有的同学就喜欢用nginx,但它又不具备ACME能力,那还能不能享受ACME的便利呢?你的nginx不支持ACME,但是我们给它加上acme.sh来弥补这一点。acme.sh是一个开源的项目,旨在帮助不支持ACME协议的web服务器完成ACME服务器的“挑战”,下载证书、更新证书并安装。另外如Apache什么的,acme.sh对它的支持与nginx是类似的。

还是一个Debian机器,名字叫web-deb-03.lab.miyunda.com,给它安装nginx:

1
2
sudo apt install nginx
systemctl status nginx

在这台机器上安装acme.sh并启动http“挑战”,第4、5两行是为当前用户添加文件夹权限,因为acme.sh脚本需要在这里放置http-01挑战的随机内容。

1
2
3
4
5
6
7
sudo apt install curl -y
curl https://get.acme.sh | sh -s email=[email protected]
source ~/.bashrc
sudo chown root:$USER /var/www/html
sudo chmod g+wx /var/www/html
acme.sh --issue --webroot /var/www/html -d web-dev-03.lab.miyunda.com \
    --server https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory

输出信息也描述了ACME挑战的过程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
[Fri Jun 14 09:07:36 PM CST 2024] Using CA: https://vault-dev.miyunda.com/v1/ca-int-x1/acme/directory
[Fri Jun 14 09:07:36 PM CST 2024] Single domain='web-dev-03.lab.miyunda.com'
[Fri Jun 14 09:07:37 PM CST 2024] Getting webroot for domain='web-dev-03.lab.miyunda.com'
[Fri Jun 14 09:07:37 PM CST 2024] Verifying: web-dev-03.lab.miyunda.com
[Fri Jun 14 09:07:37 PM CST 2024] Processing, The CA is processing your order, please just wait. (1/30)
[Fri Jun 14 09:07:40 PM CST 2024] Success
[Fri Jun 14 09:07:40 PM CST 2024] Verify finished, start to sign.
[Fri Jun 14 09:07:40 PM CST 2024] Lets finalize the order.
[Fri Jun 14 09:07:40 PM CST 2024] Le_OrderFinalize='https://vault-dev.miyunda.com/v1/ca-int-x1/acme/order/caa7b310-2244-7a86-1def-618a255f0609/finalize'
[Fri Jun 14 09:07:41 PM CST 2024] Downloading cert.
[Fri Jun 14 09:07:41 PM CST 2024] Le_LinkCert='https://vault-dev.miyunda.com/v1/ca-int-x1/acme/order/caa7b310-2244-7a86-1def-618a255f0609/cert'
[Fri Jun 14 09:07:41 PM CST 2024] Cert success.
-----BEGIN CERTIFICATE-----
MIIEszCCApugAwIBAgIUbPwZdAz7+vMgW1FFnt6jQaHBEJswDQYJKoZIhvcNAQEL
BQAwSzESMBAGA1UECxMJTWFya2V0aW5nMTUwMwYDVQQDEyx2YXVsdC1kZXYubWl5
...
cnIh1/1vixCSC3fjD3s//zwK+szqisssoI6isIH7i0qjakJ2VqtPDMNZYLD7v0Y1
e7qHKGmejw==
-----END CERTIFICATE-----~

创建相应的站点:

1
2
3
sudo mkdir /var/www/web-dev-03.lab.miyunda.com
sudo nano /var/www/web-dev-03.lab.miyunda.com/index.html # 内容自己编
sudo nano /etc/nginx/sites-enabled/web-dev-03.lab.miyunda.com.conf
1
2
3
4
5
6
7
8
server {
    listen              443 ssl;
    server_name         web-dev-03.lab.miyunda.com;
    ssl_certificate     /etc/nginx/ssl/web-dev-03.lab.miyunda.com/web-dev-03.lab.miyunda.com.crt;
    ssl_certificate_key /etc/nginx/ssl/web-dev-03.lab.miyunda.com/web-dev-03.lab.miyunda.com.key;
    root /var/www/web-dev-03.lab.miyunda.com;
    index index.html;
}

允许当前用户写入——更新证书时就有权限写文件了:

1
2
3
sudo mkdir -p /etc/nginx/ssl/web-dev-03.lab.miyunda.com
sudo chown root:$USER /etc/nginx/ssl/web-dev-03.lab.miyunda.com
sudo chmod g+wx /etc/nginx/ssl/web-dev-03.lab.miyunda.com

告诉acme.sh 如何安装证书——以后证书更新了它会自动安装:

1
2
3
4
acme.sh --installcert -d web-dev-03.lab.miyunda.com \
        --keypath /etc/nginx/ssl/web-dev-03.lab.miyunda.com/web-dev-03.lab.miyunda.com.key \
        --fullchainpath /etc/nginx/ssl/web-dev-03.lab.miyunda.com/web-dev-03.lab.miyunda.com.crt \
        --reloadcmd 'sudo systemctl reload nginx'

这时证书已经安装好了,可以用浏览器访问试试,也可以用命令行:

1
curl -kv https://web-dev-03.lab.miyunda.com

我们可以看到acme.sh给自己添加了定时任务,每天一次检查是否要续期、更新。

1
2
crontab -l
42 11 * * * "~/.acme.sh"/acme.sh --cron --home "~/.acme.sh" > /dev/null

喜欢让它们夜里做事的话可以把第二个数字改成“02”:

1
crontab -e

以上我们介绍了两种使用ACME客户端的方法,两种方法都可以做到“一劳永逸”、“配置后不管”。

好了,看过就等于会了。下一篇文章还是对我们内部的CA使用ACME,但是Kubernetes。

返场:

我们在过家家的时候往往会觉得“这CA不好,我不要了”而删除之再弄个新的,这时需要让Caddy忘记以前的信息。要不它还会傻傻地联系以前的CA。

1
2
3
sudo rm -rf /var/lib/caddy/.local/share/caddy/acme
sudo rm -rf /var/lib/caddy/.local/share/caddy/certificates
sudo systemctl reload caddy
0%