Share Notes

chundev

View the Project on GitHub latteouka/share-notes

TLS 憑證上傳功能測試手冊:從錯誤測試到正式域名上線

日期:2026-03-28 技術棧:Next.js / K8s ingress-nginx / cert-manager


TL;DR

Web 應用新增了 TLS 憑證上傳功能,管理員可在 /settings/tls 上傳自訂憑證取代 cert-manager 自簽憑證。本文記錄完整的測試計畫:先用刻意產生的錯誤檔案驗證每一層防線,再用公司正式域名做端對端實測。


測試架構總覽

憑證上傳有三道防線,每一道都需要獨立驗證:

使用者上傳檔案
  ↓
① 前端預檢(檔案大小、副檔名、PEM header)
  ↓
② 後端驗證(cert/key 配對、過期、域名比對、chain)
  ↓
③ K8s 部署 + Health Check(更新 Secret/Ingress → tls.connect 驗證)
  ↓
成功 or 自動回退

Phase 1:錯誤路徑測試

產生測試用檔案

mkdir -p test-certs && cd test-certs

# 1. 不是 PEM 格式的 .pem 檔(測前端 PEM header 檢查)
echo "這不是PEM格式的檔案" > not-pem.pem

# 2. 有 PEM header 但內容損壞(測後端解析)
echo "-----BEGIN CERTIFICATE-----
AAAA不合法的base64內容
-----END CERTIFICATE-----" > bad-content.pem

# 3. 過期憑證(測後端過期檢查)
openssl genrsa -out expired.key 2048
openssl req -new -key expired.key -subj "/CN=test.example.com" -out expired.csr
openssl x509 -req -in expired.csr -signkey expired.key \
  -not_before 20240101000000Z -not_after 20240102000000Z \
  -extfile <(echo "subjectAltName=DNS:test.example.com") \
  -out expired.pem
rm expired.csr

# 4. 域名不符的合法憑證(測後端域名比對)
openssl req -x509 -newkey rsa:2048 \
  -keyout wrong-domain.key -out wrong-domain.pem \
  -days 365 -nodes \
  -subj "/CN=wrong.example.com" \
  -addext "subjectAltName=DNS:wrong.example.com"

# 5. 合法憑證 + 不配對的 key(測後端 key mismatch)
openssl req -x509 -newkey rsa:2048 \
  -keyout valid.key -out valid.pem \
  -days 365 -nodes \
  -subj "/CN=test.example.com" \
  -addext "subjectAltName=DNS:test.example.com"
openssl genrsa -out mismatched.key 2048

測試案例清單

進入 /settings/tls 頁面,依序測試:

# 上傳什麼 域名填什麼 預期結果 擋在哪一層
1 not-pem.pem 當憑證 Toast:不是有效的 PEM 格式 ① 前端
2 改副檔名的 .txt Toast:不支援的檔案格式 ① 前端
3 bad-content.pem + 任意 key test.example.com 錯誤:無法解析憑證內容 ② 後端
4 valid.pem + mismatched.key test.example.com 錯誤:憑證與私鑰不配對 ② 後端
5 expired.pem + expired.key test.example.com 錯誤:憑證已過期 ② 後端
6 wrong-domain.pem + wrong-domain.key test.example.com 錯誤:CN/SAN 不包含域名 ② 後端
7 valid.pem + valid.key test.example.com 驗證通過 → Health check 失敗 → 自動回退 ③ K8s
8 不填任何東西 套用按鈕 disabled ① 前端

案例 7 說明: cert 和 key 都合法且域名也對,但 test.example.com 的 DNS 沒指向 K8s,所以 tls.connect health check 會連不上 → 系統自動回退到前一組憑證,頁面顯示 Alert 警告。


Phase 2:用公司域名正式測試

前提條件

要讓整個流程跑到底(包括 health check 通過),需要:

條件 說明 怎麼確認
🔑 TLS 憑證 公司 CA 簽發或購買的憑證 .pem 格式,包含 server cert + 中繼 CA chain
🔑 私鑰 對應的私鑰 .pem.key 格式
🌐 DNS 域名 A record 指向 K8s Ingress IP nslookup <domain> 解析到正確 IP
🔧 ENCRYPTION_KEY Pod 環境變數(備份加密用) 已有 SMTP 功能在用,應該已設定

步驟

1. 準備憑證

如果有正式憑證:

# 確認 cert 和 key 配對
openssl x509 -in cert.pem -noout -modulus | md5
openssl rsa -in key.pem -noout -modulus | md5
# 兩個 hash 要一樣

# 確認 cert chain 完整
openssl verify -CAfile chain.pem cert.pem

# 合併 chain(如果 cert 和 chain 是分開的)
cat cert.pem chain.pem > fullchain.pem

如果想先用自簽憑證測流程(不用正式憑證):

# 用公司域名產生自簽 cert
openssl req -x509 -newkey rsa:2048 \
  -keyout test.key -out test.pem \
  -days 365 -nodes \
  -subj "/CN=your-domain.com" \
  -addext "subjectAltName=DNS:your-domain.com"

⚠️ 自簽憑證可以測試整個流程(K8s 更新、Ingress 切換),但瀏覽器會顯示不受信任警告。

2. 設定 DNS

# 確認 DNS 已指向 K8s Ingress IP
nslookup your-domain.com
# 應該解析到 K8s 節點 IP

# 如果是內部測試,可以先改 /etc/hosts
echo "<K8S_NODE_IP> your-domain.com" | sudo tee -a /etc/hosts

⚠️ DNS 必須先設好再上傳憑證。因為 health check 會嘗試 tls.connect 到域名的 443 port,如果 DNS 解析不到 → health check 失敗 → 自動回退。

3. 上傳憑證

  1. 進入 /settings/tls
  2. 填入域名(如 your-domain.com
  3. 上傳 fullchain.pem(或 test.pem
  4. 上傳 key.pem(或 test.key
  5. 點「套用」

三階段 loading:驗證中...套用中...檢查中...

4. 驗證結果

套用成功後,狀態卡片應該更新:

命令列驗證:

# 確認 K8s Secret 已更新
kubectl get secret <tls-secret-name> -n <namespace> -o jsonpath='{.data.tls\.crt}' | \
  base64 -d | openssl x509 -noout -subject -dates

# 確認 Ingress host 已更新
kubectl get ingress <ingress-name> -n <namespace> -o jsonpath='{.spec.rules[0].host}'

# 從外部驗證 TLS
echo | openssl s_client -connect your-domain.com:443 -servername your-domain.com 2>/dev/null | \
  openssl x509 -noout -subject -issuer -dates

5. 測試回退

  1. 在狀態卡片點「回退到自簽憑證」
  2. 確認 AlertDialog 提示
  3. 點確認
  4. Badge 應該回到「自簽憑證」

已知限制

項目 說明
ingress-nginx cache 極少數情況下更新 Secret 後 Lua cache 不刷新,需重啟 ingress controller Pod
Health check 目標 連到域名的 443 port,離線環境需確保 DNS 可解析
單域名限制 目前只支援一個域名 + 一組憑證(Ingress 只有一個 rule)
瀏覽器快取 切換憑證後瀏覽器可能快取舊 TLS session,需要清除或開無痕視窗

學到的事