[Kubernetes]使用Cert-Manager自動簽發與管理TLS憑證

Automatically provision and manage TLS certificates in Kubernetes with Cert-Manager

Morpheus Huang
12 min readJul 22, 2022

0x01 Overview

在Kubernetes中,線上Web服務通常會使用HTTPS協定,而使用HTTPS協定就需面臨到TLS憑證問題。一般做法不外乎是:自簽TLS憑證受信任的CA機構簽發,前者適用於自己組織內部,後者則是用於公開的網路服務,但後者通常都需要花錢購買。

不過,目前有個Let’s Encrypt提供免費、自動化和開放的憑證頒發,讓使用者可以申請免費的SSL/TLS 憑證,但因為是免費的,所以期限只有90天,必須要每三個月進行renew。手動久了總是會有其他出口,這時就輪到Cert-Manager出場了。Cert-Manager是基於 Kubernetes 所開發用來作為憑證管理的工具,其整體架構示意圖如下:

cert-manager目前主要可支援自動簽發來自let’s encrypt、vault和venafi的憑證或者自簽憑證,並且會自動展延憑證,確保憑證一直維持在有效期內。

前陣子系統平台demo時,順便整合了cert-manager到平台中,因此寫篇文章紀錄一下,說明如何在kubernetes上,透過cert-manager + ingress來實現tls憑證的自動簽發,其主要包含透過let’s encrypt(HTTP01 Challenge)簽發的憑證以及自簽憑證兩種方式。

Let’s Encrypt支援DNS-01與HTTP-01兩種Challenge的方式來申請TLS憑證,DNS-01需要有可控的DNS,證明擁有此網域的 DNS 控制權,而HTTP-01則需要能讓Let’s Encrypt Server從外部可連線到ACME 客戶端80 Port,取得指定的TOKEN資料,詳細資訊可參考官方網站

Prerequisite

  • Kubernetes
  • Helm
  • Ingress-Controller: 需事先安裝並,本文章使用kong-ingress-controller。
  • 可從外部直接存取80 Port(Let’s Encrypt HTTP01 Challenge)

0x02 Cert-Manager

在安裝前,先講一下cert-manager需要關注的三個crd,Issuer/ClusterIssuer與Certificate。

  • Issuer/ClusterIssuer: 讓cert-manager知道要使用什麼方式簽發憑證,例如ACME、SelfSigned。Issuer與ClusterIssuer差別在於,Issuer為Namespace Scope,意即僅作用在某個命名空間中,而ClusterIssuer則為Cluster Scope,作用範圍包含整個Kubernetes Cluster。
  • Certificate: 使cert-manager 知道要憑證要使用的Domain以及簽發憑證所需要的一些設定,包含 Issuer/ClusterIssuer 的引用

在本文中,實際上只需要建立Issuer/ClusterIssuer即可,後面會再加以說明。

1.安裝cert-manager

cert-manager可以透過Helm來部署。

#1. Add the Jetstack Helm repository
~$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories
#2. Update your local Helm chart repository cache
~$ helm repo update
...Successfully got an update from the "jetstack" chart repository
#3. Install cert-manager
~$ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.8.2 --set extraArgs[0]="--enable-certificate-owner-ref=true" --set installCRDs=true
...
cert-manager v1.8.2 has been deployed successfully!
...

這裡額外設定了一個Args: --enable-certificate-owner-ref=true,主要目的是讓tls secret與Certificate綁定,當Certificate刪除時,會同步刪除tls secret,如不想要此行為,可將--set extraArgs[0]部分整個移除。

2.建立ClusterIssuer

前面提到有兩種不同scope的issuer,在這裡主要建立ClusterIssuer,內容如下:

幾個參數說明:

  • acme: 表示此ClusterIssuer透過acme協定來簽發憑證,不同方式有不同的對應值,如selfsigned、vault等。
  • acme.email: 替換自己的email
  • acme.server: acme 的伺服器端,這裡使用 Let’s Encrypt Prod Server,Prod Server有一些限制,如果是測試環境,可以改用Let’s Encrypt的測試環境 https://acme-staging-v02.api.letsencrypt.org/directory ,限制上會較為寬鬆
  • acme.solvers[0].http01: 使用 HTTP-01 的方式進行ACME 協定驗證,如先前所說,也可以視需求與環境使用DNS-01,不過設定會有所不同。
  • acme.solvers[0].http01.ingress.class: 替換為所使用的ingress-controller的ingressClass值。使用此方式,cert-manager會建立一個80 port ingress入口,提供acme伺服器端進行HTTP-01驗證。

到這裡整個cert-manager就算準備完畢,也許有人會問那前面提到的Certificate呢? 這裡並不直接手動建立Certificate來引用ClusterIssuer,進行憑證簽發,而是透過Ingress額外的設定,讓cert-manager自動替我們建立Certificate引用ClusterIssuer,並進行憑證的簽發。

0x03 部署服務與Ingress

cert-manager有個子元件ingress-shim本身會監控Ingress資源,並根據其Ingress上的annotations,與Ingress相關的設定,自動建立Certificate,並簽發憑證,在建立Ingress之前,首先先來建立一個nginx的服務。

#1. create nginx deployment
~$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
#2. expose nginx service
~$ kubectl expose deployment nginx --port 80 --name nginx
service/nginx exposed

接著需要替nginx service建立一個ingress,如下:

幾個變數需自行修改:

  • [your ingress class]: 替換成對應ingress-controller的class name
  • [your domain]: 替換成實際對外的domain,如果沒有domain,可以使用[ip address].nip.io的方式,例如:morpheus-192–168–0–1.nip.io,192–168–0–1需自行改成外部可存取的IP。
  • annotations.cert-manager.io/cluster-issuer: letsencrypt-prod,此處的值需與前面所建立的ClusterIssuer Name相同。

這裡大概說明一下跟cert-manager ingress-shim有關的幾個annotation,這幾個都是可以直接單獨使用的,不過 kubernetes.io/tls-acme 要能運作還需另外在安裝時指定default的issuer,並建立對應的資源,下面會額外解釋此部分。

  • cert-manager.io/issuer: 如果建立的是namespace scope的issuer,則需使用此annotation。
  • cert-manager.io/cluster-issuer: 類似issuer,使用clusterissuer時,需使用這個annotation,來指定要引用的clusterissuer。
  • kubernetes.io/tls-acme: 前面會特別說,都是可以單獨使用的原因,是因為不知道為什麼很多文章都把這個annotation跟上面兩個之一搭配在一起使用,然後也沒設定default issuer。實際上這個annotation主要是為了相容早期的kube-lego的使用方式,當這個annotation單獨存在時,cert-manager會直接以預設的issuer-name、issuer-kind與issuer-group來作為建立Certificate時內容,詳細可參考這裡,也就是說如果沒有設定預設使用的issuer-name、issuer-kind與issuer-group,那這個annotation是沒有作用的,而在有設定預設這些值的情況,其實只要有kubernetes.io/tls-acme就夠了,並不需要搭配其他一起使用。

也許會問說,那kubernetes.io/tls-acme與其他兩個之一搭配使用有什麼問題,基本上個人覺得從cert-manager的處理邏輯來看是沒有意義的,因為annotations中指定的issuer優先權永遠大於default issuer,文件中其實也有提到這點(如果有其他想法歡迎分享),有興趣的也可以直接看Code,了解其判斷邏輯和使用預設issuer-name、issuer-kind、issuer-group的時機。

當cert-manager ingress-shim監控到ingress中有支援的annotation時,會去判斷要使用的issuer-name、issuer-kind等資訊,然後建立Certificate進行憑證簽發,完成後會將憑證儲存在ingress中secretName所設定的名稱中,可以透過以下指令確認是否已經建立tls憑證的secret。

~$ kubectl get secret
...
morpheus-***-***-***-***.nip.io kubernetes.io/tls 2 30m

也可以將secret內的tls.crt經過解開來透過openssl查看所簽發的憑證內容。

# base64 decode
~$ kubectl get secret morpheus-***-***-***-***.nip.io -o jsonpath='{.data.tls\.crt}' | base64 -d > tls.crt
# view certificate
~$ openssl x509 -in tls.crt -text

或者透過瀏覽器也是可以瀏覽憑證內容,且會發現瀏覽器左上鎖頭顯示憑證是有效的。

到這裡,透過cert-manager與ingress來自動簽發TLS憑證就算是完成,如果將ingress資源刪除,因為前面有設定 --enable-certificate-owner-ref=true 的關係,因此certificate與secret都會一併被刪除。

當ingress刪除時,Certificate會被刪除,而當Certificate被刪除時,Secret會跟著被刪除。

這個機制主要是Kubernetes ownerReferences的使用,有興趣可以參考這裡

上面整個部分主要是透過Let’s encrypt acme協定來實現憑證的簽發,那如果在內部想用自簽憑證的方式又該如何設定,其實很簡單,把ClusterIssuer中的acme改成selfsigned,設定如下參考:

接著,將ingress中annotation值改成 self-signed

cert-manager.io/cluster-issuer: self-signed

重新apply ingress後,就會產生自簽的憑證了。

0x04 結論

有了cert-manager後,在Kubernetes服務需要暴露https的服務時,就不用煩惱憑證簽發與過期的問題,配合Ingress的annotation,實際上僅需要安裝cert-manager與建立對應的issuer,剩下的cert-manager都會自動幫忙搞定,著實方便許多。

另外,本文僅針對實際上有使用到的Let’s encrypt HTTP-01 Challenge和self-signed部分進行說明,像是Let’s encrypt還有DNS-01 Challenge或是其他的家如Vault的簽發設定方式,有興趣可以自行參考文件試試。

0x05 參考資料

--

--