ACME(Automatic Certificate Management Environment)是一種用于自動化處理X.509數字證書簽發請求的協議。通過ACME協議,可以自動驗證證書申請者的域名所有權,然后為其簽發證書。Let's Encrypt是一個非營利性的公共CA(證書頒發機構),支持ACME協議,它可以簽發一般瀏覽器信任的證書。本文介紹如何使用cert-manager對接Let's Encrypt,為ASM網關簽發瀏覽器信任的HTTPS證書。
前提條件
已添加ACK集群到ASM實例,且ASM實例版本為1.16及以上。具體操作,請參見添加集群到ASM實例和升級ASM實例。
已部署入口網關,并暴露80和443端口。具體操作,請參見創建入口網關。
已在ASM實例關聯的集群中部署httpbin應用。具體操作,請參見部署httpbin應用。
已安裝cert-manager。具體操作,請參見在集群中安裝cert-manager。
已在ASM網關上啟用Ingress API支持,具體操作,請參見步驟一:在目標網關上啟用Ingress。
cert-manager中的ACME說明
在使用cert-manager時,ACME Issuer組件負責向支持ACME協議的CA Server注冊用戶賬戶。創建ACME Issuer的過程中,cert-manager將為您生成一個私鑰,這個私鑰專門用于與ACME CA Server進行安全通信。公共CA(如Let's Encrypt)頒發的證書通常被客戶端(如Web瀏覽器)所信任。這意味著當用戶通過瀏覽器訪問您的網站時,他們會自動信任該網站的SSL/TLS證書。公共CA頒發證書的主要目的是為了向瀏覽器證明當前服務器確實是該域名的合法服務提供者。為此,公共CA在頒發證書前需要核實申請證書的服務器實際上控制著該域名。更多關于ACME協議的定義和細節,請參見Automatic Certificate Management Environment。
Solving Challenges
挑戰(Challenges)是ACME協議中用于驗證證書申請者對目標域名所有權的關鍵機制。在申請證書的過程中,ACME CA Server會要求客戶端(證書申請者)完成特定的挑戰,以確保只有域名的合法所有者能成功申請對應的證書,以此來提高網絡安全性,避免域名冒充的風險。cert-manager支持兩種主要的挑戰類型:HTTP-01 Challenge和DNS-01 Challenge。
HTTP-01 Challenge依賴于服務器在特定HTTP URL上公開由ACME客戶端生成的驗證密鑰。該密鑰應位于一個可公網訪問的URL中,其路徑需包含待申請證書的域名。當ACME CA Server能按約定路徑從互聯網上成功檢索到該密鑰,即認為申請者對目標域名具備有效控制權。為了簡化這一過程,當設置了HTTP-01Challenge,cert-manager會自動調整集群的Ingress設置,將指向該驗證密鑰URL的請求導向一個專門設立的小型Web服務。該服務承載著所需的驗證密鑰,用于響應ACME服務器發起的挑戰驗證。
DNS-01 Challenge是通過在DNS系統中發布一條包含特定計算得出密鑰的TXT記錄來完成驗證。當這條TXT記錄在全球DNS緩存中傳播生效后,ACME CA Server可通過標準DNS查詢來檢索該密鑰,從而確認申請者對指定域名的實際所有權。在此過程中,具備適當權限的cert-manager會自動為所使用的DNS服務商生成并提交所需TXT記錄,以滿足DNS-01 Challenge的要求。
實際場景中,您需要確認您當前的證書頒發機構是否支持ACME協議。如果支持,則ASM網關可以通過cert-manager自動從CA處獲取證書。例如,Sectigo目前已經支持了ACME協議,其原理與本文所述類似,詳情請參見Overview。
步驟一:準備公網域名
要使用Let's Encrypt為域名簽發證書,首先需要有一個公網域名,并且需要您將公網域名指向要使用的ASM網關。具體流程,請參見您的DNS提供商的文檔。如果您使用的是阿里云提供的DNS服務,請參見添加解析記錄。關于Let's Encrypt的說明,請參見Getting Started。
步驟二:創建對接Let's Encrypt的Issuer資源
使用數據面集群的KubeConfig,創建如下資源。
apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-prod-issuer namespace: istio-system spec: acme: email: 'te**@mail.com' # 這個不是必填字段,但是建議填寫。ACME Server可能通過該郵箱向您發送與證書相關的重要通知。 privateKeySecretRef: name: letsencrypt-prod server: https://acme-v02.api.letsencrypt.org/directory solvers: - http01: ingress: ingressClassName: istio
上述Issuer資源指定了一個
http01
類型的Solver,使用Ingress API,ingressClassName
為istio
。以下步驟將解釋該Solver如何生效。說明cert-manager支持使用Ingress API和Gateway API兩種類型的Solver。ASM也支持這兩種API。本示例使用Ingress API。
等待Issuer就緒,使用以下命令,查看Issuer狀態。
kubectl -n istio-system get issuer letsencrypt-prod-issuer
預期輸出:
NAME READY AGE letsencrypt-prod-issuer True 8m3s
步驟三:為ASM網關簽發證書
使用數據面集群的KubeConfig,創建如下資源。
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: istio-ingressgateway-certs namespace: istio-system spec: dnsNames: - ${測試域名} # test.com issuerRef: group: cert-manager.io kind: Issuer name: letsencrypt-prod-issuer secretName: istio-ingressgateway-certs
等待Certificate就緒,使用以下命令,查看Certificate狀態。
kubectl -n istio-system get certificate istio-ingressgateway-certs
預期輸出:
NAME READY SECRET AGE istio-ingressgateway-certs True istio-ingressgateway-certs 59m
證書簽發過程解析
創建Certificate資源之后,cert-manager會使用創建的Issuer為這個域名簽發證書。這個過程中,配置的Solver就會生效。
Let's Encrypt將會使用HTTP-01 Challenge來確認:當前Server是這個域名的所有者。為此,Let's Encrypt會發送一個HTTP請求到這個域名,然后它需要獲取到一個合法的返回值,才可以完成驗證。在本示例中,網關上只有httpbin的路由規則,并沒有任何和Challenge有關的配置。Let's Encrypt是否真的發送了Challenge請求?請求如何被響應?
您可以執行以下命令,查看網關日志,判斷Let's Encrypt是否真的發送了Challenge請求。
kubectl -n istio-system logs ${網關Pod名稱} | grep letsencrypt | tail -1
{
"authority_for": "xxxxxxx",
"bytes_received": "0",
"bytes_sent": "87",
"downstream_local_address": "xx.xx.xx.xx:80",
"downstream_remote_address": "xx.xx.xx.xx:57101",
"duration": "0",
"istio_policy_status": "-",
"method": "GET",
"path": "/.well-known/acme-challenge/JfKvfdSNmkR7UqmCQU0OSkJC3EsnP4ZUiCc28OLLLxA",
"protocol": "HTTP/1.1",
"request_id": "e6806d08-0469-4383-be8e-4d7506b39ec5",
"requested_server_name": "-",
"response_code": "200",
"response_flags": "-",
"route_name": "-",
"start_time": "2024-04-08T12:04:06.153Z",
"trace_id": "-",
"upstream_cluster": "outbound|8089||cm-acme-http-solver-c4ch9.istio-system.svc.cluster.local",
"upstream_host": "xx.xx.xx.xx:8089",
"upstream_local_address": "xx.xx.xx.xx:55886",
"upstream_response_time": "0",
"upstream_service_time": "0",
"upstream_transport_failure_reason": "-",
"user_agent": "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)",
"x_forwarded_for": "xx.xx.xx.xx"
}
可以看到網關上確實存在Let's Encrypt發出的請求,并且該請求被正常返回。在Issuer中配置了ingressClassName
為istio
。cert-manager會自動創建一個ingressClassName
為istio
的Ingress資源,將Challenge請求轉發給cert-manager對應的Solver來完成驗證。
在Certificate就緒之后,使用kubectl -n istio-system get ingress
無法看到相關的Ingress資源,是因為cert-manager在完成證書簽發后,會自動刪除Solver相關的資源,包括Ingress、Service、Deployment等資源。
步驟四:驗證Let's Encrypt簽發的證書
創建如下網關規則,在網關的443端口上配置步驟三生成的證書。具體操作,請參見管理網關規則。
apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: httpbin-https namespace: default spec: selector: istio: ingressgateway servers: - hosts: - ${測試域名} port: name: https number: 443 protocol: HTTPS tls: credentialName: istio-ingressgateway-certs mode: SIMPLE
修改原有的httpbin-vs虛擬服務為以下內容。具體操作,請參見管理虛擬服務。
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: httpbin-vs namespace: default spec: gateways: - httpbin - httpbin-https # 新增這一行。 hosts: - '*' http: - name: test route: - destination: host: httpbin.default.svc.cluster.local port: number: 8000
在瀏覽器中訪問
https://${測試域名}
。可以看到如下形式的瀏覽器地址欄,單擊圖標,顯示連接安全,表明瀏覽器已經信任您的證書。