自托管对象存储的三种 TLS 签发:自签、Let‘s Encrypt、内网 CA 的选择与轮换

对象存储装完之后通常先跑 HTTP,等要接进生产才想起证书这件事。三种常见签发路径(自签、Let's Encrypt、内网 CA)各适配不同场景,选错了不会立刻出问题,但会在轮换那一天集中爆出来。这篇文章把三条路各自怎么配、证书放哪、轮换时容易踩的坑一次说清,示例以 RustFS 为主,关键差异处对照 MinIO 官方文档。

先把结论放在最前面:自签适合开发,Let's Encrypt 适合公网域名,内网 CA 适合多节点生产集群。三条路径的配置成本都不高,但轮换机制差别很大,选错了会在几个月后付出代价。证书过期这件事在多数监控体系里不是默认项,很多人是等业务侧报 TLS 握手失败才想起来,所以轮换机制比签发本身更值得提前设计。

先把证书该放哪、该叫什么名说清楚

RustFS 的 TLS 配置非常简单,一个环境变量加一对固定命名的文件:

bash 复制代码
RUSTFS_TLS_PATH="/opt/tls"

证书文件必须叫 rustfs_cert.pem 和 rustfs_key.pem,放在 RUSTFS_TLS_PATH 指向的目录里,路径可以自选。重启后 TLS 同时作用于两个监听端口:S3 API(9000)和 Console(9001)。

Docker 场景下有两个官方明确提到的坑:一是要挂载证书路径,二是容器以 rustfs 用户运行,证书文件属主必须是 rustfs,否则启动时会因权限问题读不到私钥。启动之后可以用自带子命令确认解析状态:

bash 复制代码
rustfs tls inspect --path /opt/tls

这个命令检查目录布局和证书解析是否正常,排障时比看日志更直接。证书文件名固定这件事看起来是个小限制,实际好处是排障时少了一个变量:路径对、文件名对、权限对,这三点核对完基本就能定位大部分 TLS 启动失败,不用在日志里翻原因。

换成新证书时有个动作值得固化成习惯:不要直接覆盖写这两个文件。用 > 重定向去写正在被监听的证书,进程完全可能在只写入一半的时刻读到它,解析失败,热重载拿到的是一个残缺的 PEM。正确姿势是先写临时文件再原子重命名:

bash 复制代码
install -m 0644 new-cert.pem /opt/tls/.rustfs_cert.pem.tmp
mv -f /opt/tls/.rustfs_cert.pem.tmp /opt/tls/rustfs_cert.pem

同一文件系统内的 mv 是一次 rename,对读者来说只有改名前后两个状态,不存在中间态。cp 覆盖式写不行,那是先截断再写。这条对重启路径同样适用:覆盖写留下的半截文件会让启动时读证书这一步失败,日志里只留一句看不出所以然的解析错误。私钥按 0600 落盘,具体见轮换那节。

对照 MinIO 的做法:默认目录是 $HOME/.minio/certs,文件名是 public.crt 和 private.key,不支持 PFX 打包格式,私钥有密码时需设 MINIO_CERT_PASSWD 且私钥必须是 PKCS-1 格式。多证书场景下 MinIO 用子目录按主机名分开,通过 SNI 自动选证书,这套逻辑 RustFS 目前没有对等实现。

两条路径其实传递的是同一个思路:证书目录里的文件名是契约,改了就找不到。RustFS 把契约写得更死(固定文件名),换来的是排障更简单;MinIO 留了更多灵活度(多证书 SNI),代价是多一层目录结构要维护。

自签:最快跑通,但有一个坑要记

自签证书的优点是五分钟就能跑通,缺点是客户端默认不信任它,除非额外处理。

生成自签证书的 openssl 命令通常长这样:

bash 复制代码
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 \
  -nodes -keyout rustfs_key.pem -out rustfs_cert.pem \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

生成完把两个文件放进 RUSTFS_TLS_PATH 指向的目录,属主改成 rustfs,重启就能跑。SAN 里的 DNS/IP 要覆盖实际访问时用的地址,客户端会按 SAN 校验,只写 CN 不写 SAN 的话新版客户端会报证书不匹配。

RustFS 针对自签场景给了一个专门的开关:RUSTFS_TRUST_LEAF_CERT_AS_CA,默认 false,官方描述是「像信任 CA 一样信任叶证书(用于自签配置)」。另一个相关变量是 RUSTFS_TRUST_SYSTEM_CA,默认 false,官方描述里明确写了出站 TLS 连接也会信任操作系统 CA 库。带「出站」这个限定,反过来看 RUSTFS_TRUST_LEAF_CERT_AS_CA 动的是入站那一侧的信任池。

这个开关在生产集群上不建议开。官方环境变量参考里没有「仅开发环境」的标注,别拿这个当依据,问题出在机制上:它把一枚本来不具备签发能力的叶证书放进了信任链的位置,而自签部署里这份叶证书往往正是分发给应用客户端的那一份。在这个模型下,任何持有该证书的人都能被服务端当作可信来源校验。节点互信要靠下一节自建 CA 的完整链路,不是靠把叶证书冒充成 CA。

MinIO 的处理更自动化一些:自签证书的公钥会被自动加到启动时的 CA 信任池,所以自签证书不需要额外拷贝到 CAs/ 目录也能工作。但官方明确说这是便利性行为,生产环境还是应该把签发 CA 证书放到 CAs/ 目录,而不是依赖这个自动机制。

自签还有一个容易在轮换时爆的坑,下一节单独讲。

Let's Encrypt:公网域名才划算

Let's Encrypt 免费且自动续期,前提是你要有一个公网可解析的域名。certbot 有两种常见模式,差别在于怎么完成域名验证:

  • --standalone:certbot 自己监听 80 端口完成验证,需要临时停掉占用 80 端口的服务
  • --webroot:往已运行 web 服务的 .well-known/acme-challenge/ 目录写验证文件,不用停服务

对象存储服务本身占用 9000/9001,跟 80 端口不冲突,所以两种模式都能跑。用 --standalone 最省事,前提是签发那几分钟 80 端口是空闲的。

RustFS 官方文档没有给 certbot 的完整流程,但在 Kubernetes 部署的 Helm Chart 文档里给了 cert-manager 的集成示例,用的是 letsencrypt-prod 这个 ClusterIssuer 名字,通过 ingress 注解 cert-manager.io/cluster-issuer 触发证书签发。走 K8s 部署的话这条路径是官方支持的。

拿到的 fullchain.pem 和 privkey.pem 需要重命名成 RustFS 认的那两个文件名,做个软链或者签发后脚本改名都可以。用软链确实省事,certbot 续期是重写源文件,链接本身不动,热重载感知到的是目录内容变了。

但软链有两个地方会翻车。一是覆盖写会把链接替换掉:mv 的目标是符号链接时,被换掉的是链接而不是链接指向的文件,等下次续期往原路径写入,RustFS 读到的还是那个已经被变成普通文件的旧链接,指向的是一份早已作废的证书。要么用 cp -P 走链接写入,要么续期脚本里先 rm 再建。二是容器环境:bind mount 会把符号链接原样带进容器,但链接指向的 /etc/letsencrypt/live/... 这棵树在容器里通常不存在,进去就是个断链。挂载真实文件、或者每次续期后重建链接,比依赖软链稳。

Let's Encrypt 证书 90 天有效期,自动续期之后记得让存储进程感知到新证书,这就引出下一节。

内网 CA:生产环境的长期解

如果对象存储只在内网提供服务,Let's Encrypt 反而不合适(域名解析、80 端口暴露都是额外的攻击面),这时自己搭一套内部 CA 是更常见的选择。

MinIO 官方文档对这条路径的描述最完整,值得逐条记:

  • 把 CA 证书放到 CAs/ 目录,所有节点的 public.crt 都从这个 CA 签发
  • 之后只轮换 public.crt 和 private.key,CAs/ 目录不再动
  • 轮换时 MinIO 自动重载,无需重启,也不影响信任池
  • 自己生成的 CA 就够用,不必买公共 CA

RustFS 对应的配置路径是通过 Helm Chart 的 mTLS 支持:Chart 会自动创建自签根 CA、namespace Issuer、server 和 client 证书,挂载后通过 RUSTFS_SERVER_MTLS_ENABLE=1 和 RUSTFS_TLS_PATH=/opt/tls 启用。这个路径覆盖了内网双向 TLS 场景,如果不在 K8s 里跑,就需要自己用 openssl 或 cfssl 生成 CA 和叶证书,再放到 RUSTFS_TLS_PATH 指向的目录。

mTLS 相关的三个变量各自管的是不同半边,混着理解最容易出事。RUSTFS_SERVER_MTLS_ENABLE 默认 false,官方环境变量参考里的描述是「要求服务器监听器提供客户端证书(双向 TLS)」,限定词在监听器上,不是只挂在节点互访那一条路径上。Helm Chart 的 mTLS 页是按 Pod 间通信来讲的,但同一页末尾专门给了一条外部访问提醒:启用之前先确认 Ingress 控制器或别的外部客户端怎么提交证书。两处合起来读,结论是普通 S3 客户端一样会被要求提交证书,不是「只针对节点之间」。

剩下两个 RUSTFS_MTLS_CLIENT_CERT、RUSTFS_MTLS_CLIENT_KEY,官方描述写的是「节点间 mTLS 连接提供的客户端证书」,管的是 RustFS 作为客户端往外连的那一段。多节点部署时这几个要一致配置,否则节点间通信会失败。

还有一处容易被漏掉:Chart 文档里写明健康探针也会用生成的那套客户端证书。手工启用 mTLS 却没配套客户端凭证时,最先报错的往往是探针而不是业务请求,表现为 Pod 一直起不来或者反复重启,很容易误判成证书格式问题。

轮换那一下:怎么做才不会打挂服务

证书过期那天如果没准备好,往往比首次配置更狼狈。MinIO 官方文档里有一段专门讲轮换陷阱的原文,值得整段抄过来理解:

AIStor reloads the certificate it presents whenever the certificate files change, but it builds its CA trust pool once, at process start, and never rebuilds it.

翻译一下:证书文件变化时 MinIO 会自动重载它对外呈现的证书,但 CA 信任池只在进程启动时构建一次,之后不会重建。这意味着如果你换掉了节点证书但没有重启进程,节点之间会突然互不信任,这也是自签证书最常踩的坑。

官方推荐的解法是把 CA 放进 CAs/ 一次,之后只轮换叶证书,信任池不用动。

替换顺序有讲究:先换私钥,再换公证书。如果反过来先换公证书,会出现一个短暂窗口期证书和私钥不匹配,导致 TLS 握手失败。开了热重载之后这个窗口依然存在,只是触发条件变了:它不再取决于谁先写入,而取决于热重载什么时候扫到目录。官方环境变量参考只给了 RUSTFS_TLS_RELOAD_ENABLE 这个开关和 false 的默认值,轮询间隔那一档没写在参考里(源码常量整理里提到默认 30 秒、最小 5 秒,这一项建议在自己版本上确认)。所以写完新证书不等于立刻生效,中间这段时间新私钥配的是旧证书,演练时别只看进程状态,要实际发起一次 TLS 握手,看协商出来的证书有效期对不对。

私钥权限也在演练清单里。RustFS 官方 Docker 说明只要求证书文件属主是 rustfs 用户,没规定权限位;0600 是通用做法,容器里进程以非 root 的 UID 10001 运行,权限放开了等于把私钥摊给同主机其他用户。

RustFS 侧的对应机制是 RUSTFS_TLS_RELOAD_ENABLE,默认 false,显式设为 true 后会监听证书目录并热重载。开启之后轮换流程变成:新证书写进 RUSTFS_TLS_PATH → 存储检测到变化 → 自动加载。不开这个开关的话,每次换证书都要重启进程。

热重载的可靠性和重启是两回事。重启是一次干净的加载,失败会有明确的启动报错;热重载加载失败时的表现要盯紧,最可能的一种是进程照旧跑着、内存里那份旧证书继续对外服务。这类故障最麻烦的地方恰恰在于它不报警:服务 up、健康探针绿、日志里没有异常行,只有证书停留在上一个周期,一直停到过期那天变成大面积握手失败。所以开了热重载之后,第一次轮换要专门观察一次,确认新证书真被加载了,再把它当稳定机制依赖。

巡检要靠命令而不是进程状态。rustfs tls inspect --path <DIR> 是官方 CLI 里查证书目录布局和解析状态的子命令,把它放进巡检脚本,读输出里的有效期和解析结果,比任何「服务是否在跑」的判断都更早发现问题。RustFS 的 rustfs diagnose 用来分析日志文件、给出 probable failure causes,热重载失败留下的线索可以往里丢。

证书过期监控也值得提前做。MinIO 暴露 minio_system_network_certificate_expires_in 指标(单位秒),官方告警阈值是 30 天 > 2592000、7 天 > 604800。RustFS 这边有没有对等指标要看具体版本,暂时把 rustfs tls inspect 的巡检查成定时任务,作为过渡手段。

三种签发的选择顺序

三种路径不是并列选项,按场景挑:

  • 本机开发、内网快速验证:自签最省事,开启 RUSTFS_TRUST_LEAF_CERT_AS_CA 或对应客户端的 --insecure 选项就行
  • 公网可访问、有域名:Let's Encrypt 加 certbot,用 --webroot 避免停服务
  • 内网生产、多节点集群:自建内部 CA,一次放进信任池,之后只轮换叶证书

还有一条混合场景值得单独说:内网服务 + 少量外部访问。这种情况可以走内网 CA 覆盖内部节点,同时给对外的一个入口节点用 Let's Encrypt 签证书,两套证书并存。MinIO 通过 SNI 支持多证书并存,按主机名自动匹配,这套能力在内网为主、对外少量暴露的场景下很有用。

RustFS 在 Apache 2.0 许可下开源,TLS 配置、环境变量、rustfs tls inspect 命令的完整说明都在 docs.rustfs.com。首次配完之后建议立刻跑一次 rustfs tls inspect --path,确认证书解析正常再进生产。

相关推荐
GitCode官方1 小时前
“开源共生 共赢未来”2026北京昇腾生态先锋中心系列活动之算子实操工坊成功举办
人工智能·开源·昇腾·atomgit
Li-Yongjun2 小时前
fuser
运维
分布式存储与RustFS2 小时前
把现有 S3 工具链直接接到 RustFS:100% 兼容到底覆盖哪些 API
云原生·开源·对象存储·分布式存储·s3·rustfs·性能基准
William Dawson2 小时前
kkFileView 内网 ARM 服务器全链路部署:从「找不到 office」到全绿通关(超详细排障实录)
运维·服务器
2601_962219012 小时前
操作日志全留存架构:万象生鲜系统生鲜业务合规审计底层实现方案
微服务·云原生·架构
海宇数据2 小时前
零信任架构实战:基于海宇车型识别精准构建自动化违章处理网关
运维·人工智能·架构·自动化
wflynn2 小时前
GitHub 日榜趋势速报 | 2026-09-28
开源·github
xbzb2 小时前
Linux SELinux 安全加固与标签排错手册
linux·运维·安全
johnny2332 小时前
Dromara开源社区项目简介:hutool、MaxKey、Jpom、JQuick-Curl、Warm-Flow
开源