TLS Certificates for Internal Services: Split-Horizon DNS and ACME Implementation
內部服務的 TLS 憑證:分割視域 DNS 與 ACME 實作
內部 TLS 的挑戰
為內部服務加上 TLS 通常只能在兩種方案間做二選一:使用自簽憑證或使用公證憑證。自簽憑證可以避免洩漏內部網路資訊,但必須讓每一台客戶端裝置手動信任自訂的根 CA,這在管理上相當繁重。公證憑證則預設受信任,但通常需要服務在公共互聯網上可達,以便完成驗證。
為內部服務實作分割視域 DNS
分割視域 DNS 允許同一個域名根據請求者的位置解析到不同的 IP 位址。對於內部服務而言,這意味著像 grafana.tuxnet.dev 這樣的域名會對外部解析器返回公共 IP(以滿足 CA 驗證),而對 VPN 內部的客戶端則返回私有內部 IP。
技術架構
要實作此工作流程,需要以下元件:
- 具 DNS 解析功能的 VPN:可使用 NetBird 之類的工具管理自訂區域,確保 VPN 連線的客戶端取得內部 IP,而伺服器本身則使用公共 DNS 以便簽發憑證。
- ACME 客戶端:
acme.sh可用於自動向 Let's Encrypt 或 ZeroSSL 申請憑證。 - 反向代理 / WAF:Nginx 可作為入口點,僅綁定在 VPN 網路介面上,確保只有來自 VPN 的流量能存取內部服務,充當 Web Access Firewall(WAF)。
憑證簽發與續期
在獨立模式下使用 acme.sh 的 http-01 挑戰,讓客戶端僅在簽發過程中佔用 80 埠,這樣 Nginx 就不必永久佔用 80 埠。
為了自動續期,可設定 cron 工作執行 acme.sh --cron,然後將憑證同步至 Nginx 目錄並重新載入服務。為避免以 root 身份執行 ACME 客戶端,可對 socat 設定 setcap CAP_NET_BIND_SERVICE=+ep,讓非特權使用者也能綁定 80 埠。
使用 SAN 與 CNAME 進行擴展
與其為每個內部服務各自簽發憑證,不如使用主體備選名稱(SAN)。只要建立一張包含多個 DNS 名稱(例如 internal.tuxnet.dev、grafana.tuxnet.dev、analytics.tuxnet.dev)的憑證,即可在多個 Nginx server block 中共用同一憑證檔案。
替代方案與取捨
雖然分割視域 DNS 解決了客戶端信任的問題,但許多工程師仍提出其他方法,以避免「分裂大腦」的 DNS 行為與內部主機名稱的洩漏。
DNS-01 驗證
許多實務者建議使用 DNS-01 挑戰取代 HTTP-01。DNS-01 驗證允許 CA 透過公共 DNS 中的 TXT 記錄驗證域名所有權,這意味著內部服務根本不需要公開可路由或擁有公共 IP 位址,從而完全不需要分割視域 DNS。
通配符憑證
通配符憑證(*.domain.com)常被用來避免將特定內部子域名洩漏至憑證透明度(CT)日誌。雖然有人認為若私鑰外洩,通配符憑證的安全風險較高,但也有人認為在內部基礎設施中,單一負載平衡器處理所有終端的情況下,使用通配符更為實用。
內部 CA 與 mTLS
在高安全性環境下,通常會使用內部 CA(例如 step-ca),確保內部主機名稱永不出現在公共 CT 日誌中。此外,實作雙向 TLS(mTLS)可提供比標準 TLS 更強的身份保證,因為客戶端必須向伺服器出示憑證,將安全性從網路層(VPN)提升到身份層。
比較摘要
| 方法 | 客戶端信任 | DNS 複雜度 | 隱私(CT 日誌) | 網路暴露 |
|---|---|---|---|---|
| 分割視域 | 自動 | 高 | 低(會洩漏名稱) | 低(透過 WAF) |
| DNS-01 | 自動 | 低 | 低(會洩漏名稱) | 無 |
| 內部 CA | 手動/MDM | 低 | 高(私有) | 無 |
| mTLS | 手動/MDM | 低 | 高(私有) | 無 |
摘要:
使用分割視域 DNS 與 ACME 為內部服務實作 TLS,可在不需要客戶端信任自簽根 CA 的情況下取得公證 CA 簽發的憑證,但同時也帶來 DNS 複雜度與憑證透明度日誌方面的取捨。