Skip to content

OpenSSL 自簽憑證產生

以 OpenSSL 腳本產生自簽 CA 憑證與伺服器憑證,適用於內部測試環境與私有服務的 HTTPS 設定。

概述

自簽憑證(Self-signed Certificate)是由自建的 CA 根憑證簽發的 TLS 憑證。它不在公共 CA 信任鏈內,所以瀏覽器預設不信任——但只要將自建的 CA 根憑證匯入用戶端信任庫,就能建立完整的 HTTPS 加密與身分驗證,適合內部系統、開發測試環境、Kubernetes 叢集內部通訊等場景。

現代瀏覽器的限制: Chrome、Firefox 等瀏覽器已強制要求憑證包含 Subject Alternative Name(SAN),僅設定 Common Name(CN)已無法通過驗證。本文腳本透過 v3.ext 設定檔正確填入 SAN。

核心內容

自簽憑證的兩層架構

標準做法是建立兩層憑證結構:

  1. CA 根憑證ca.crt):自簽,代表「信任根」。需手動匯入每個用戶端的信任庫(一次性操作)。
  2. 伺服器憑證server.crt):由 CA 根憑證簽發,實際部署在伺服器。

好處是:只要將 CA 根憑證匯入一次,之後由同一個 CA 簽發的所有伺服器憑證都會被自動信任,不需重複匯入。

v3 擴充設定(SAN 的必要性)

v3.ext 設定檔的作用是為伺服器憑證加入 X.509 v3 擴充,其中最關鍵的是 subjectAltName(SAN):

ini
authorityKeyIdentifier=keyid,issuer   # 標示簽發者資訊
basicConstraints=CA:FALSE              # 宣告此憑證非 CA,不可再簽發
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names            # SAN:瀏覽器驗證的必要欄位

[alt_names]
DNS.1 = 10.22.101.202.sslip.io        # 憑證對應的域名
IP.1  = 10.22.101.202                 # IP 位址直接存取時也需要 IP SAN

缺少 SAN 的憑證在現代瀏覽器中會觸發 ERR_CERT_COMMON_NAME_INVALID 錯誤。同時加入 DNS 與 IP 兩種 SAN 條目,可確保不論用域名或 IP 存取服務都能通過驗證。

伺服器憑證有效期

CA 根憑證可設定較長有效期(例如 3600 天),因為只有在輪替 CA 時才需要重新將根憑證發送給所有用戶端。伺服器憑證有效期應設為 397 天——這是 Apple/Chrome 政策規定的最長信任期限,超過此天數的憑證在部分平台會被標記為不受信任,即使技術上尚未過期。

sslip.io 的應用技巧

sslip.io 是一個公共 DNS 服務,自動將嵌入 IP 的域名請求解析回該 IP(例如 10.22.101.202.sslip.io10.22.101.202)。

這讓你可以用域名格式(而非純 IP)申請憑證,解決某些工具或瀏覽器不接受純 IP 憑證(subjectAltName = IP:...)的問題。

關鍵要點

  • SAN(v3.ext)是現代瀏覽器的強制要求,不可省略
  • CA 根憑證需手動匯入用戶端信任庫,否則仍顯示不安全警告
  • CA 根憑證有效期可設為 3600 天(約 10 年),伺服器憑證應設 397 天(Chrome/Apple 最長信任期限)
  • 同時加入 DNS.1IP.1 SAN,確保域名與 IP 直接存取都能通過驗證
  • sslip.io 讓 IP 位址也能以域名格式申請憑證
  • 私鑰(ca.keyserver.key)需妥善保管,不可外洩

實際應用

  • Kubernetes TLS Secret:將 server.crtserver.key 打包為 K8s TLS Secret,供 Ingress 使用
  • 本地開發 HTTPS:搭配 Nginx 或 Caddy 設定本地 HTTPS 環境
  • 內部服務:不需公開可信憑證的私有服務(可搭配 VPN 或 Cloudflare Tunnel 的 Full Strict 模式使用)
  • 年度輪替 SOP 可參考 維運 SOP:憑證與帳密更新

部署設定參考

以下為實際部署時使用的完整設定,供日後查詢與複製使用。

環境參數

參數
Domainsslip.io
Host IP10.22.101.202
Common Name (CN)10.22.101.202.sslip.io
憑證有效天數3600(約 10 年)
金鑰長度RSA 2048 bit

產出檔案說明

檔案用途是否需要保留
ca.keyCA 私鑰是(保密保管)
ca.crtCA 根憑證是(匯入用戶端信任庫)
server.key伺服器私鑰是(部署至伺服器)
server.crt伺服器憑證是(部署至伺服器)
server.csr憑證簽署請求否(過渡檔案可刪除)
v3.extSAN 設定檔否(過渡檔案可刪除)

完整產生腳本

bash
#!/bin/bash
set -euo pipefail

DOMAIN="sslip.io"
HOST="10.22.101.202"
COMMON_NAME=$HOST.$DOMAIN

mkdir -p ssl
cd ssl

# 1. 產生 CA 私鑰
openssl genrsa -out ca.key 2048

# 2. 產生自簽 CA 憑證(3600 天,約 10 年)
openssl req -x509 -new -nodes -key ca.key \
  -subj "/CN=My rootCA/O=${DOMAIN}" \
  -sha256 -days 3600 -out ca.crt

# 3. 產生伺服器私鑰與 CSR
openssl req -new -newkey rsa:2048 -sha256 -nodes \
  -keyout server.key \
  -subj "/CN=${COMMON_NAME}" \
  -out server.csr

# 4. 建立 v3 擴充設定檔(含 DNS + IP 雙 SAN)
cat << EOF > v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = $COMMON_NAME
IP.1  = $HOST
EOF

# 5. 使用 CA 簽發伺服器憑證(397 天,符合瀏覽器最長信任限制)
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -extfile v3.ext \
  -sha256 -out server.crt -days 397

操作指令

bash
# 驗證憑證內容(確認 SAN 欄位存在)
openssl x509 -in ssl/server.crt -text -noout | grep -A2 "Subject Alternative Name"

# 驗證憑證由指定 CA 簽發
openssl verify -CAfile ssl/ca.crt ssl/server.crt

# 將 CA 根憑證建立為 K8s TLS Secret(ca 憑證信任鏈)
kubectl create secret generic ca-cert --from-file=ca.crt=ssl/ca.crt -n <namespace>

# 將伺服器憑證建立為 K8s TLS Secret
kubectl create secret tls tls-cert \
  --cert=ssl/server.crt \
  --key=ssl/server.key \
  -n <namespace>

部署至 Nginx

nginx
ssl_certificate     /path/to/ssl/server.crt;
ssl_certificate_key /path/to/ssl/server.key;

匯入 CA 憑證至 Ubuntu 信任庫

bash
sudo cp ssl/ca.crt /usr/local/share/ca-certificates/my-rootca.crt
sudo update-ca-certificates

若要讓瀏覽器或系統信任此自簽憑證,必須將 ca.crt 匯入作業系統或瀏覽器的「受信任根憑證授權單位」清單。

相關概念

來源