在 Linkerd 中使用 mTLS 保護應用程序通信
安全性是雲原生應用程序的重中之重,雖然安全性是一個非常廣泛的話題,但 Linkerd 依然可以發揮重要作用:其雙向 TLS(mTLS)功能是爲了在 Kubernetes 中實現零信任的安全方法。零信任安全是一種 IT 安全模型,要求試圖訪問專用網絡上資源的每一個人和每一臺設備(無論位於網絡邊界之內還是之外)都必須進行嚴格的身份驗證。
什麼是 mTLS
在雲環境中越來越普遍的通信安全方法是零信任方法,雖然對零信任安全的全面處理超出了本節的範圍,但核心目標是將應用程序的安全邊界縮小到儘可能小的級別。例如,與其在數據中心周圍設置防火牆,對進入的流量實施安全保護,留下 "軟內部" 而不進一步驗證,不如讓數據中心的每個應用在自己的邊界實施安全。這種零信任的方法自然適合雲環境,因爲雲環境中的底層硬件和網絡基礎設施不在你的控制之下。
Linkerd 安全模型通過在服務之間提供透明的雙向 TLS 通信來實現零信任安全,雙向 TLS (mTLS) 是一種傳輸安全形式,可提供通信的機密性和身份驗證。換句話說,不僅通信被加密,而且身份也在連接的兩端進行驗證(mTLS 的雙向組件與瀏覽器使用的 TLS 不同,它只驗證連接的服務器端,mTLS 驗證客戶端和服務器的身份)。Linkerd 的目標是通過使用 mTLS 對 Kubernetes Pod 之間的通信進行身份驗證和加密,允許你對 Kubernetes 集羣採用零信任方法。
驗證是特別重要的,雖然大多數人認爲 TLS 的價值在於加密,但驗證連接兩邊實體的身份也同樣重要。畢竟只有在你能相信與你通信的另一方的實體是他們所說的人的情況下,加密纔是有用的 -- 來自不良行爲者的加密信息仍然是來自不良行爲者的信息。
在 Linkerd 中,通過 mTLS 驗證的身份與 Kubernetes ServiceAccounts 相關聯。這意味着 Linkerd 的 mTLS 身份系統使用與 Kubernetes 用於爲集羣上的工作負載建立身份和訪問控制的完全相同的模式,而不是發明一個新框架。
使用 Linkerd 的 mTLS
Linkerd 的設計原則之一是,複雜性是安全的敵人。配置東西越難,使用它的可能性就越小; 選項和設置越多,就越有可能不小心以不安全的方式進行配置。
默認情況下,Linkerd 爲所有網格中的 pod-to-pod
通信啓用了 mTLS,只要雙方都注入了數據平面代理,那麼恭喜你:你已經在服務之間驗證了加密的 mTLS。事實上,前面我們使用的 Emojivoto
應用程序中就已經在使用 mTLS 了,只是我們沒有意識到而已。
對對於 Linkerd 自動添加 mTLS 的功能,有幾個需要注意的地方。
-
兩個端點都必須在網格中。Linkerd 需要同時處理客戶端和服務器端的連接才能發揮其 mTLS 的魔力。
-
在 Linkerd 2.8.1 和更早的版本中,Linkerd 只能爲 HTTP 和 gRPC 流量添加 mTLS,即使如此,也無法針對某些類型的權限、主機或 Header 執行該操作,這些限制在 Linkerd 2.9 中已經被移除,它將 mTLS 添加到所有的 TCP 流量中,不管是什麼協議。
-
客戶端發起 TLS 的連接不能由 Linkerd 進行 mTLS。相反,Linkerd 會將把這些連接視爲 TCP 流量。請注意,這也意味着 Linkerd 只能爲這些連接提供 TCP 級別的指標。
接下來讓我們來了解下 mTLS 是如何工作的,以及如何驗證我們的連接是否確實具有 mTLS。
Linkerd Identity 組件
在前面講解 Linkerd 架構的時候我們就討論過 Linkerd 控制平面的 Identity
組件,它作爲 CA 或證書頒發機構所扮演的角色。證書頒發機構是頒發數字證書並使身份組件成爲數字證書頒發者的實體。
Linkerd 使用的證書與網站用來驗證其身份的 TLS 證書 “類型” 相同。與網站不同,這些證書不經過 Verisign
等第三方實體的驗證,因爲它們不需要驗證,它們僅供 Linkerd 代理在集羣內使用。
Linkerd 的 CA(Identity 服務)作爲 Linkerd 控制平面的一部分部署到集羣中。在該部署過程中,Linkerd CLI 將生成一個證書並將其存儲在 Linkerd 命名空間中名爲 linkerd-identity-token-XXXXX
的 Kubernetes Secret 中。
$ kubectl get secret -n linkerd
NAME TYPE DATA AGE
linkerd-identity-issuer Opaque 2 11d
linkerd-identity-token-nqhbk kubernetes.io/service-account-token 3 11d
# ......
通過查看 Secret 可以看到一個前綴爲 linkerd-identity-token-
的 Secret 對象,我們可以將其導出來進行查看:
$ kubectl get secret -n linkerd linkerd-identity-token-nqhbk -o yaml
apiVersion: v1
data:
ca.crt: <ca.crt>
namespace: bGlua2VyZA==
token: <token>
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: linkerd-identity
kubernetes.io/service-account.uid: cdc3d8fd-0e02-4b17-88ce-d2cc0a9e4907
name: linkerd-identity-token-nqhbk
namespace: linkerd
type: kubernetes.io/service-account-token
輸出的數據部分中名爲 ca.crt
的字段就是在 Linkerd 安裝期間生成的 UTF-8 編碼的根證書。此證書稱爲 “信任之錨”,因爲它是用作頒發給代理的所有證書的基礎。
信任錨還用於在安裝時創建另一個證書和密鑰對:頒發者憑據,這些存儲在名爲 linkerd-identity-issuer
的單獨 Kubernetes Secret 中。頒發者憑據用於向 Linkerd proxy 頒發證書,同樣我們也可以來查看該 Secret 的數據。
$ kubectl get secret -n linkerd linkerd-identity-issuer -o yaml
apiVersion: v1
data:
crt.pem: <crt.pem>
key.pem: <key.pem>
kind: Secret
metadata:
labels:
linkerd.io/control-plane-component: identity
linkerd.io/control-plane-ns: linkerd
name: linkerd-identity-issuer
namespace: linkerd
type: Opaque
接下來我們將瞭解如何使用這些密鑰向代理頒發證書以啓用 mTLS。
Linkerd 代理如何獲取證書
首先,當 Pod 被注入 Linkerd 代理時,該代理會向 Linkerd 的身份服務發送證書籤名請求 (CSR)。身份服務使用頒發者憑據向該代理頒發簽名證書(CSR 的作用域是運行 Pod 的 Kubernetes ServiceAccount,因此生成的證書與該 ServiceAccount 相關聯),證書將在 24 小時後過期。在證書過期前,代理向身份服務發送新的證書籤名請求,獲取新證書;這個過程在 Linkerd 代理的整個生命週期內都會持續,這稱爲證書輪換,是一種將證書泄露造成的損失降至最低的自動化方式:在最壞的情況下,任何泄露的證書只能使用 24 小時。
linkerd check
命令有一種簡單的方法來確保代理都具有由身份服務頒發的證書,我們可以自己通過傳遞 --proxy
標誌來檢查代理的狀態。
# 使用 --proxy 標誌在數據平面檢查代理
$ linkerd check --proxy
# ......
linkerd-identity
----------------
√ certificate config is valid
√ trust anchors are using supported crypto algorithm
√ trust anchors are within their validity period
√ trust anchors are valid for at least 60 days
√ issuer cert is using supported crypto algorithm
√ issuer cert is within its validity period
√ issuer cert is valid for at least 60 days
√ issuer cert is issued by the trust anchor
linkerd-identity-data-plane
---------------------------
√ data plane proxies certificate match CA
# ......
Status check results are √
上面的輸出結果中包括一個 linkerd-identity-data-plane
部分,用來指示代理是否正在使用由信任錨頒發的證書。
linkerd-identity-data-plane
---------------------------
√ data plane proxies certificate match CA
我們也可以在 Linkerd 代理和身份服務中開啓 debug 模式,以查看代理將 CSR 發送到身份服務並取回證書。
$ kubectl get deploy -n linkerd
NAME READY UP-TO-DATE AVAILABLE AGE
linkerd-destination 1/1 1 1 11d
linkerd-identity 1/1 1 1 11d
linkerd-proxy-injector 1/1 1 1 11d
$ kubectl edit deploy linkerd-identity -n linkerd
# ......
spec:
containers:
- args:
- identity
- -log-level=debug # 設置爲 debug 模式
- -log-format=plain
- -controller-namespace=linkerd
- -identity-trust-domain=cluster.local
- -identity-issuance-lifetime=24h0m0s
- -identity-clock-skew-allowance=20s
- -identity-scheme=linkerd.io/tls
# ......
當身份服務重新更新後,我們可以觀察對應 Pod 的日誌信息:
$ kubectl logs -f -n linkerd deploy/linkerd-identity -c identity
上面的命令會輸出很多日誌到控制檯,在 Linkerd 身份日誌輸出中,我們可以看到 Request Body
和 Response Body
相關的輸出,其中包括一個很長的 UTF-8 編碼數據,即 CSR
和頒發給代理的證書:
# ......
time="2022-08-30T07:39:17Z" level=debug msg="Issuer has been updated"
time="2022-08-30T07:39:17Z" level=info msg="starting admin server on :9990"
time="2022-08-30T07:39:17Z" level=info msg="starting gRPC server on :8080"
time="2022-08-30T07:39:17Z" level=debug msg="Validating token for linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local"
I0830 07:39:17.291787 1 request.go:1123] Request Body: {"kind":"TokenReview","apiVersion":"authentication.k8s.io/v1","metadata":{"creationTimestamp":null},"spec":{"token":"<......>"},"status":{"user":{}}}
# ......
I0830 07:39:17.291969 1 round_trippers.go:435] curl -k -v -XPOST -H "Accept: application/json, */*" -H "Content-Type: application/json" -H "User-Agent: controller/v0.0.0 (linux/amd64) kubernetes/$Format" -H "Authorization: Bearer <masked>" 'https://10.96.0.1:443/apis/authentication.k8s.io/v1/tokenreviews'
I0830 07:39:17.293883 1 round_trippers.go:454] POST https://10.96.0.1:443/apis/authentication.k8s.io/v1/tokenreviews 201 Created in 1 milliseconds
# ......
I0830 07:39:17.294241 1 request.go:1123] Response Body: {"kind":"TokenReview","apiVersion":"authentication.k8s.io/v1","metadata":{"creationTimestamp":null,"managedFields":[{"manager":"controller","operation":"Update","apiVersion":"authentication.k8s.io/v1","time":"2022-08-30T07:39:17Z","fieldsType":"FieldsV1","fieldsV1":{"f:spec":{"f:token":{}}}}]},"spec":{"token":"<token>"},"status":{"authenticated":true,"user":{"username":"system:serviceaccount:linkerd:linkerd-identity","uid":"cdc3d8fd-0e02-4b17-88ce-d2cc0a9e4907","groups":["system:serviceaccounts","system:serviceaccounts:linkerd","system:authenticated"],"extra":{"authentication.kubernetes.io/pod-name":["linkerd-identity-5d9b874d66-m77ps"],"authentication.kubernetes.io/pod-uid":["2f147d37-1616-487a-b7aa-1805b84c026a"]}},"audiences":["https://kubernetes.default.svc.cluster.local"]}}
# ......
接下來我們再來查看下 Emojivoto
應用服務之間的安全性。首先我們可以使用 linkerd viz edges
命令來查看下 Pod 之間是如何連接的。
$ linkerd viz edges po -n emojivoto
SRC DST SRC_NS DST_NS SECURED
vote-bot-6d7677bb68-jvxsg web-5f86686c4d-58p7k emojivoto emojivoto √
web-5f86686c4d-58p7k emoji-696d9d8f95-5vn9w emojivoto emojivoto √
web-5f86686c4d-58p7k voting-ff4c54b8d-xhjv7 emojivoto emojivoto √
prometheus-7bbc4d8c5b-5rc8r emoji-696d9d8f95-5vn9w linkerd-viz emojivoto √
prometheus-7bbc4d8c5b-5rc8r vote-bot-6d7677bb68-jvxsg linkerd-viz emojivoto √
prometheus-7bbc4d8c5b-5rc8r voting-ff4c54b8d-xhjv7 linkerd-viz emojivoto √
prometheus-7bbc4d8c5b-5rc8r web-5f86686c4d-58p7k linkerd-viz emojivoto √
我們可以看到上面的輸出結果中最後包含一列 SECURED
,表示是否是安全的連接,下面的值均爲 √
,表示是安全的連接。
然後我們再次使用 linkerd viz tap
命令來捕獲實時流量,在輸出的信息中也包含一個 tls=true
的標籤值,如下所示:
$ linkerd viz tap deploy web -n emojivoto
req id=0:0 proxy=in src=10.244.1.165:47130 dst=10.244.1.176:8080 tls=true :method=GET :authority=web-svc.emojivoto:80 :path=/api/list
req id=0:1 proxy=out src=10.244.1.176:42096 dst=10.244.1.188:8080 tls=true :method=POST :authority=emoji-svc.emojivoto:8080 :path=/emojivoto.v1.EmojiService/ListAll
rsp id=0:1 proxy=out src=10.244.1.176:42096 dst=10.244.1.188:8080 tls=true :status=200 latency=2731µs
# ......
到這裏面我們就瞭解了 Linkerd 的 Identity 組件如何向數據平面中的 Linkerd 代理頒發證書,以及 Linkerd 在代理中的 mTLS 實現如何使用這些證書來加密通信並驗證雙方的身份。
自動輪換控制器平面 TLS 憑證
Linkerd 的自動 mTLS 功能使用一組 TLS 憑據爲代理生成 TLS 證書:信任錨 (trust anchor)、頒發者證書(issuer certificate) 和私鑰(private key)。雖然 Linkerd 每 24 小時自動輪換數據平面代理的 TLS 證書,但它不會輪換用於頒發這些證書的 TLS 憑據。接下來我們來了解下如何使用 Cert-manager
進行自動輪換頒發者證書和私鑰。
Cert-manager
Cert-manager
是一個非常流行的雲原生證書管理工具。Cert-manager
將證書和證書頒發者作爲 CRD 資源類型添加到 Kubernetes 集羣中,簡化了獲取、更新和使用這些證書的過程。它可以從各種受支持的來源發佈證書,包括 Let's Encrypt
、 HashiCorp Vault
和 Venafi
以及私有 PKI。它將確保證書有效並且是最新的,並嘗試在證書到期前的配置時間更新證書。
cert manager
Cert-manager
的安裝也非常簡單,可以直接使用官方提供的資源清單文件一鍵安裝,如下所示:
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml
默認會將相關資源安裝到一個名爲 cert-manager
的命名空間中:
$ kubectl get pods -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-55649d64b4-kdcfk 1/1 Running 0 43s
cert-manager-cainjector-666db4777-cp2dn 1/1 Running 0 43s
cert-manager-webhook-6466bc8f4-5lvw5 1/1 Running 0 43s
頒發證書
接下來我們使用 step
工具創建一個簽名密鑰對,並將其存儲在 Kubernetes 的一個 Secret 對象中。
$ step certificate create root.linkerd.cluster.local ca.crt ca.key \
--profile root-ca --no-password --insecure
Your certificate has been saved in ca.crt.
Your private key has been saved in ca.key.
然後將生成的 ca.crt
和 ca.key
保存到 Secret 對象中:
$ kubectl create secret tls linkerd-trust-anchor --cert=ca.crt --key=ca.key -n
secret/linkerd-trust-anchor created
有了 Secret,我們可以創建一個引用密鑰的證書頒發者 Issuer
資源:
$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: linkerd-trust-anchor
namespace: linkerd
spec:
ca:
secretName: linkerd-trust-anchor
EOF
$ kubectl get issuer -n linkerd
NAME READY AGE
linkerd-trust-anchor True 84s
然後我們就可以頒發證書了,並將它們寫入到一個 Secret 對象中,最後,我們可以創建一個 cert-manager "Certificate" 資源, 它使用這個 Issuer 來生成所需的證書:
$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: linkerd-identity-issuer
namespace: linkerd
spec:
secretName: linkerd-identity-issuer
duration: 48h
renewBefore: 25h
issuerRef:
name: linkerd-trust-anchor
kind: Issuer
commonName: identity.linkerd.cluster.local
dnsNames:
- identity.linkerd.cluster.local
isCA: true
privateKey:
algorithm: ECDSA
usages:
- cert sign
- crl sign
- server auth
- client auth
EOF
$ kubectl get certificate -n linkerd
NAME READY SECRET AGE
linkerd-identity-issuer True linkerd-identity-issuer 17s
在上面的資源清單文件中,duration
指示 cert-manager 將證書視爲有效期 48 小時,而 renewBefore
指示 cert-manager 將嘗試在當前證書到期前 25 小時頒發新證書。
此時,cert-manager 現在可以使用此證書資源獲取 TLS 憑據,該憑據將存儲在名爲 linkerd-identity-issuer
的 Secret 中,要驗證您新頒發的證書,我們可以運行下面的命令:
$ kubectl get secret linkerd-identity-issuer -o yaml -n linkerd
apiVersion: v1
data:
ca.crt: <ca.crt>
crt.pem: <crt.pem>
key.pem: <key.pem>
tls.crt: <tls.crt>
tls.key: <tls.key>
kind: Secret
metadata:
name: linkerd-identity-issuer
namespace: linkerd
type: Opaque
現在我們只需要通知 Linkerd 使用這些憑據就可以了。由於我們是通過 linkerd
命令行工具進行安裝的,Linkerd 控制平面默認會通過 --identity-external-issuer
標誌進行安裝,該標誌指示 Linkerd 從 linkerd-identity-issuer
的 Secret 讀取證書。每當更新存儲在 Secret 中的 certificate
和 key
時, identity 服務將自動檢測此更改並重新加載新憑據。
這樣我們就設置了 Linkerd 控制平面 TLS 憑據的自動輪換,如果你想監控更新過程,你可以檢查服務發出的 IssuerUpdated
事件:
$ kubectl get events --field-selector reason=IssuerUpdated -n linkerd
LAST SEEN TYPE REASON OBJECT MESSAGE
2m37s Normal IssuerUpdated deployment/linkerd-identity Updated identity issuer
可以看到已經執行了 Updated identity issuer
。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/i1QgROeXs3LNWIlXhe62EQ