在 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 的功能,有幾個需要注意的地方。

接下來讓我們來了解下 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 BodyResponse 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 EncryptHashiCorp VaultVenafi 以及私有 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.crtca.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 中的 certificatekey 時, 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