使用 Cert Manager 自動管理 Kubernetes Gateway 證書
背景
Kubernetes Gateway
或許,確切地應該稱為 Kubernetes Gateway API[1] (下稱 GWAPI)。GWAPI 是由 SIG-NETWORK[2] 社區管理的開源項目,是一種規范,不提供實現。Gateway API 被認為是 Kubernetes Ingress API[3] 的繼任者,在 2019 年的 Ingress 革命[4] 中首次被提出,并在 2020 年 11 月發布了第一個版本。GWAPI 的發展非常快,截止本文發出已經演進到了 1.0.0 版本[5],并且 1.1.0 也即將發布(已經發布了 1.1.0-rc2[6])。
GWAPI 是一種管理 Kubernetes 集群內部和集群外部流量的方法。它提供了一個更精細、更模塊化的方法來描述路由和網關配置,相比于傳統的 Ingress API,GWAPI 設計上更為靈活和可擴展。
cert-manager
cert-manager[7] 是一個專門為 Kubernetes 設計的自動化證書管理工具,它簡化了在 Kubernetes 集群中使用 X.509 證書的管理過程。通過 cert-manager,用戶可以輕松地為 Kubernetes 中的各種資源,如服務、應用程序和內部通信、自動頒發、更新和管理 SSL/TLS 證書。這對于確保數據傳輸的安全性非常重要。
cert-manager 主要有以下特性:
- 自動證書頒發和續期:cert-manager 可以自動處理證書的申請、續期和生命周期管理。
- 支持多種頒發源:cert-manager 支持多種證書頒發機構(CA),包括自簽的證書;通過 ACME 使用公共的 CA 如 Let's Encrypt;企業自建的 CA 系統,如 HashiCorp Vault[8]、銅鎖的 RustyVault[9] 的 PKI 等。
- Kubernetes 原生集成:作為 Kubernetes 的原生組件,cert-manager 可以使用 CRD 來定義頒發者 Issuer、證書 Certificate 等資源。
尤其是最后這點,cert-manager 可以便捷地與 Kubernetes 生態的其他組件無縫集成,比如本文要介紹的與 K8s Gateway 的整合。
Why cert-manager?
在 Kubernetes 環境中,Gateway 作為整個集群的入口,是管理入站流量的重要組件。在眾多功能特性中,安全性尤為突出和重要。其中,通信加密是確保數據安全的首要措施。通過使用 TLS/SSL 證書,Gateway 確保所有進出集群的數據都是加密的。這不僅保護了數據免受竊聽和篡改,還確保了客戶端和服務之間的通信是安全的。除了這種單向 SSL 加密以外,還可以配置為支持雙向的 SSL 加密來實現端到端的安全驗證。
而使用 cert-manager 來管理 Kubernetes Gateway 的證書提供了許多優勢,顯著提升操作效率、增強系統安全性,并且能夠以可擴展和可維護的方式支持企業級的部署和管理:
- 自動證書管理:開發者和管理員可以減少手動介入,從而降低因手動處理證書相關任務(如忘記續期)而導致的錯誤和服務中斷風險。這些都可以通過 cert-manager 的自動化來完成。
- 集成外部 CA:cert-manager 支持多種證書頒發機構(CA),包括 Let's Encrypt、HashiCorp Vault、Venafi 等。這使得在 Kubernetes 環境中使用商業或自建的 CA 變得容易,并能確保證書的合規性和認證。
- 安全和合規性:自動化證書輪換和短期證書的使用,增強安全性和合規性(自動更新證書意味著攻擊者竊取的證書只在很短的有效期內有用)。
- 與 Kubernetes 原生集成:與操作 GWAPI 的 CRD 一樣,cert-manager CRD 的操作方式對開發者友好,也能夠與 GWAPI 無縫集成。
演示說明
- FSM Gateway[10] 作為 Gateway API 的實現[11] 之一,也是 FSM[12] 整個生態中的眾多組件之一,擔負著 Kubernetes 入口流量管理的重任。在這次演示中,我們將采用 FSM Gateway 作為網關實施方案,利用其 TLS 卸載[13] 功能來增強上游服務的安全性。
- 前面提到 cert-manager 可以支持 多種頒發源[14],這里為了操作簡便,選擇最簡單的自簽發的 CA 證書。
圖片
前提條件
- K8s 集群 >=1.22
- kubectl
- helm
安裝 FSM Gateway
參考 FSM Gateway 的安裝文檔[15],我們使用 CLI 進行安裝,當前 FSM 的最新版本為 1.2.4。
system=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m | sed -E 's/x86_/amd/' | sed -E 's/aarch/arm/')
release=v1.2.4
curl -L https://github.com/flomesh-io/fsm/releases/download/$release/fsm-$release-$system-$arch.tar.gz | tar -vxzf -
./$system-$arch/fsm version
執行下面的命令安裝。
fsm install \
--set=fsm.fsmGateway.enabled=true
安裝成功后,可以看到已經注冊了 GatewayClass fsm-gateway-cls。
kubectl get gatewayclass
NAME CONTROLLER ACCEPTED AGE
fsm-gateway-cls flomesh.io/gateway-controller True 106s
安裝 cert-manager
cert-manager 的安裝可以通過 kubectl manifest 或者 helm chart 來安裝,我們這里選擇 helm chart。 注意在安裝時記得安裝 CRD。
helm repo add jetstack https://charts.jetstack.io --force-update
helm repo update
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.14.5 \
--set installCRDs=true
由于使用自簽名的 CA 證書,在創建頒發源之前先自簽 CA 證書。
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 365000 \
-key ca-key.pem \
-out ca-cert.pem \
-subj '/CN=flomesh.io'
將 CA 證書保存在 Secret ca-key-pair 中,要指定命名空間 cert-manager。
kubectl create secret generic -n cert-manager ca-key-pair \
--from-file=tls.crt=./ca-cert.pem \
--from-file=tls.key=./ca-key.pem
這里我們直接創建一個 ClusterIssuer,類型為 ca,指定為我們上面創建的 secret ca-key-pair。
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: ca-issuer-sample
spec:
ca:
secretName: ca-key-pair
EOF
一切就緒之后,接下來讓我們來部署示例應用進行測試。
部署示例應用
創建一個 httpbin 服務。
kubectl create namespace httpbin
cat <<EOF | kubectl apply -n httpbin -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kennethreitz/httpbin
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
selector:
app: httpbin
ports:
- protocol: TCP
port: 8080
targetPort: 80
EOF
簽發 TLS 證書
要簽發 TLS 證書,我們需要創建一個 CR Certificate。cert-manager 會監控該資源,然后通過資源的配置簽發證書,并保存在字段 secretName 指定的 secret 中。
- 證書保存在 secret simple-gateway-cert 中。
- 證書有效期 duration 設置為 60m
- 證書輪換的提前時間 renewBefore 設置為 59m,也就是每分鐘輪換一次。
- 通用名稱 commonName 設置為 foo.example.com
- 還有最重要的指定證書的頒發源為 ClusterIssuer ca-issuer-sample
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: simple-gateway-cert
namespace: httpbin
spec:
secretName: simple-gateway-cert
duration: 60m
renewBefore: 59m
subject:
organizations:
- flomesh-io
commonName: foo.example.com
issuerRef:
name: ca-issuer-sample
kind: ClusterIssuer
group: cert-manager.io
EOF
Certificate 創建完成后,我們可以在命名空間 httpbin 下找到 cert-manager 創建的 secert,類型為 kubernetes.io/tls。
kubectl get secret simple-gateway-cert
NAME TYPE DATA AGE
simple-gateway-cert kubernetes.io/tls 3 20s
有了證書之后,就可以為 httpbin 服務暴露 HTTPS 的訪問入口了。
創建網關和路由
為了向集群外暴露 httpbin 服務,需要為其創建網關和路由:
- 監聽器監聽在端口 8000
- 協議設置為 TLS
- 證書通過 secret simple-gateway-cert 提供
- 還有最重要的模式設置為 Terminate
cat <<EOF | kubectl apply -n httpbin -f -
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: simple-fsm-gateway
spec:
gatewayClassName: fsm-gateway-cls
listeners:
- name: https
port: 8000
protocol: HTTPS
tls:
certificateRefs:
- kind: Secret
name: simple-gateway-cert
mode: Terminate
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: http-route-foo
spec:
parentRefs:
- name: simple-fsm-gateway
port: 8000
hostnames:
- foo.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: httpbin
port: 8080
EOF
測試
如果不提供 CA 證書直接訪問,會遇到類似下面的問題。
curl https://foo.example.com/headers --connect-to foo.example.com:443:$GATEWAY_IP:8000
curl: (60) SSL certificate problem: unable to get local issuer certificate
指定 CA 證書然后再次訪問,同時添加參數 -v 可以發現請求成功。
curl --cacert ca-cert.pem https://foo.example.com/headers --connect-to foo.example.com:443:$GATEWAY_IP:8000 -v
* Connecting to hostname: 198.19.249.153
* Connecting to port: 8000
* Trying 198.19.249.153:8000...
* Connected to 198.19.249.153 (198.19.249.153) port 8000
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: ca-cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
* subject: O=flomesh-io; CN=foo.example.com
* start date: May 8 14:59:33 2024 GMT
* expire date: May 8 15:59:33 2024 GMT
* common name: foo.example.com (matched)
* issuer: CN=flomesh.io
* SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://foo.example.com/headers
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: foo.example.com]
* [HTTP/2] [1] [:path: /headers]
* [HTTP/2] [1] [user-agent: curl/8.4.0]
* [HTTP/2] [1] [accept: */*]
> GET /headers HTTP/2
> Host: foo.example.com
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/2 200
< server: gunicorn/19.9.0
< date: Wed, 08 May 2024 15:00:07 GMT
< content-type: application/json
< content-length: 141
< access-control-allow-origin: *
< access-control-allow-credentials: true
<
{
"headers": {
"Accept": "*/*",
"Connection": "keep-alive",
"Host": "foo.example.com",
"User-Agent": "curl/8.4.0"
}
}
* Connection #0 to host 198.19.249.153 left intact
從輸出的 debug 內容中我們還可以找到 TLS 握手的詳細信息。比如證書有效期 start date: May 8 14:59:33 2024 GMT 和 expire date: May 8 15:59:33 2024 GMT。
等待 1 分鐘后再次請求,你將會看到證書信息已經更新。此時,如果檢查 Certificate 資源,可以確認證書確實發生了輪換。
kubectl describe certificate simple-gateway-cert
.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Issuing 61s cert-manager-certificates-trigger Renewing certificate as renewal was scheduled at 2024-05-08 15:03:33 +0000 UTC
Normal Requested 61s cert-manager-certificates-request-manager Created new CertificateRequest resource "simple-gateway-cert-1"
Normal Issuing 1s cert-manager-certificates-trigger Renewing certificate as renewal was scheduled at 2024-05-08 15:04:33 +0000 UTC
Normal Reused 1s (x2 over 61s) cert-manager-certificates-key-manager Reusing private key stored in existing Secret resource "simple-gateway-cert"
Normal Requested 1s cert-manager-certificates-request-manager Created new CertificateRequest resource "simple-gateway-cert-2"
Normal Issuing 1s (x2 over 61s) cert-manager-certificates-issuing The certificate has been successfully issued
總結
使用 cert-manager 自動管理 Kubernetes Gateway 證書可以顯著提升安全性和管理效率。通過自動化的證書處理,如自動續期和輪換,cert-manager 減輕了管理負擔并降低了人為錯誤,確保了持續的系統安全。這一工具的集成和靈活性使其成為維護 Kubernetes 集群安全的理想選擇。
參考資料
[1] Kubernetes Gateway API: https://gateway-api.sigs.k8s.io/
[2] SIG-NETWORK: https://github.com/kubernetes/community/tree/master/sig-network
[3] Kubernetes Ingress API: https://kubernetes.io/docs/concepts/services-networking/ingress/
[4] Ingress 革命: https://static.sched.com/hosted_files/kccncna19/a5/Kubecon%2520San%2520Diego%25202019%2520-%2520Evolving%2520the%2520Kubernetes%2520Ingress%2520APIs%2520to%2520GA%2520and%2520Beyond%2520%255BPUBLIC%255D.pdf
[5] 1.0.0 版本: https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.0.0
[6] 1.1.0-rc2: https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.1.0-rc2
[7] cert-manager: https://cert-manager.io
[8] Vault: https://www.vaultproject.io
[9] RustyVault: https://github.com/Tongsuo-Project/RustyVault
[10] FSM Gateway: https://fsm-docs.flomesh.io/guides/traffic_management/ingress/fsm_gateway/
[11] Gateway API 的實現: https://gateway-api.sigs.k8s.io/implementations/
[12] FSM: https://fsm-docs.flomesh.io/overview/
[13] TLS 卸載: https://fsm-docs.flomesh.io/guides/traffic_management/ingress/fsm_gateway/tls_termination/
[14] 多種頒發源: https://cert-manager.io/docs/configuration/issuers/
[15] FSM Gateway 的安裝文檔: https://fsm-docs.flomesh.io/guides/traffic_management/ingress/fsm_gateway/installation/