Azure - AKS - 使用 AAD Pod Identity 進行 Azure 服務驗證

29 November 2020 — Written by Sky Chang
#Azure#Container#Kubernetes#AAD#Linux#AKS#Identity

前言

在 Azure 世界裡面,有非常多的資源可以使用,多到連小弟也數不清了。但無論有多少資源,還是要有權限的概念。所以,當限制資源的存取,其實就很重要。像以前,主要會透過 Service Principal 來定義,而現在,很多會採用 Managed Identity 來處理,甚至在 App Service 或是 VM 的情境,也只要在 Portal 按一下按鈕就解決了。

但是啊但是,我們的服務現在不是在 VM 層級,而是到了 Container 層級,也就是 Pod 層級,所以必須讓 Pod 能使用 Managed Identity 等機制,也因此產生了這篇文章。

Note : 目前查了資料,像 Flux 都還沒整合 AAD-Pod-Identity,所以如果是期許這些套件能整合,可能要失望了,這邊的使用上,還是偏向自己寫的應用可以輕鬆地串接 Azure 相關服務。另外,請注意,這篇也不會寫到如何用 ASP.NET Core 串接 Managed Identity。

預設的情況

首先,我們先來看看,預設 AKS 的 IAM 大概是長怎樣,順便紀錄一下。

當我們建立 AKS 的時候 ( 假設名稱為 test-aks ),並啟用 Managed Identity 的情況,當然就會在 Managed Identity 產生一筆。

2020 11 29 14 03 13

而同樣的如果在開始建置的時候,和 ACR 進行關聯,底下就會看到和 ACRPull 的權限被加入進來。

2020 11 29 14 05 40

如果想知道這個 Identity ID,除了透過 UI 外,也可以透過底下指令。

# 將 Identity id 輸出
az aks show -g study4dev-je -n aks-study4dev-je --query identityProfile.kubeletidentity.clientId -otsv

而我們也知道,AKS 其實是分成兩個資源,一個是 AKS 自己,一個是放置 AKS 資源的資源群組,裡面主要是 VMSS,而 VMSS 的這個資源全組,則是會放置 AKS 這個應用程式,讓他為這個 Resource Group 的參與者,所以自然的,tes-aks 這個 AKS 應用就可以輕易的去調整 VM ( node ) 的數量等資訊。

2020 11 29 14 00 45

而對於 AKS 的 Managed Identity 有非常的多,詳細可以參考官網,這篇文章主要列舉幾個。

  • AKS 叢集名稱-agentpool : 主要處理 Azure Container Registry (ACR) 資源
  • Ingress application gateway : 主要處理 Application Gateway 資源
  • omsagent : 主要處理 AKS metrics 到 Azure Monito
  • Virtual-Node (ACIConnector) : 主要處理 ACI 所需要的網路資源
  • aad-pod-identity : 可讓應用程式使用 AAD 來安全地存取雲端資源

所以,落落長的寫了一堆,但其實我們可以發現,在 AKS 裡面,要存取 Azure 的資源,還是靠 Managed Identity 來處理,而如果要讓 Pod 進行這些事情,我們就要使用到 Open Source 的 aad-pod-identity 來處理了。

而這篇文章,就會將重點放到 aad-pod-identity。

aad-pod-identity

如果資源都在 AKS 裡面,可能很方便,可以透過 Role 和 RoleBind 來處理權限 ( 謎之聲 : 其實還是很複雜啊啊啊!!),但如果資源離開了 AKS,就變得更複雜了,例如,要存取 Azre Storage 或是 SQL Database,可能就需要透過 SAS Token,或是帳號密碼等等。 ( 不是 Node 層級存取 ACR )

但 Azure 裡面,本來就有 Managed Identity 啊!!,有沒有什麼辦法可以透過此機制,讓 Pod 能存取呢?

答案就是 AAD Pod Identity,他可以使 Kubernetes 使用Azure Active Directory 安全訪問資源。並使用 Kubernetes原生的配置身份和綁定來匹配 Pod。而無需進行任何代碼修改,算是一個非常棒的解決方案。

而它的原理,主要是透過 Managed Identity Controller ( MIC ) 來處理。

Kubernetes 控制器會去監控 Pod 的改變。當偵測到改變的時候,MIC 會根據需要添加或刪除 AzureAssignedIdentity。換言之,當 Pod 被排程進入的時候,在創建階段,MIC 會分配 Identity 給底層的 VM/VMSS,而當所以使用該身份的 Pod 被刪除後,它也從VM/VMSS 中刪除該身份。

創建或刪除 AzureIdentity 或 AzureIdentityBinding 時,MIC 也會執行類似的操作。

當 AzureIdentity 和AzureIdentityBindings 更改,MIC將根據需要添加或刪除AzureAssignedIdentity。

開始前的權限設定

首先,我們需要給 AKS 節點資源群組適當的權限,這樣才能在 VM/VMSS 上面分配,或是取消分配身份,你可以透過官方的role-assignment.sh 來處理,但小弟這邊直接將重點擷取出來,主要內容就是將上述的 AKS 叢集名稱-agentpool 的 Managed Identity 給予 Managed Identity Operator 和 Virtual Machine Contributor 權限到 Node Resource Group ( 也就是 AKS 的 VMSS 放置的位置 )。

Note : 若你的 Managed Identity 是自己建立,放置於其他位置,請參考 aad-pod-idetity 的官網處理。

# 取得 ID
ID="$(az aks show -g study4dev-je -n aks-study4dev-je --query identityProfile.kubeletidentity.clientId -otsv)"

# 針對 AKS 的節點資源群組,給予 Managed Identity Operator 權限
az role assignment create --role "Managed Identity Operator" --assignee "${ID}" --scope "/subscriptions/${SUBSCRIPTION_ID}/resourcegroups/${NODE_RESOURCE_GROUP}"

# 針對 AKS 的節點資源群組,給予 Virtual Machine Contributor 權限
az role assignment create --role "Virtual Machine Contributor" --assignee "${ID}" --scope "/subscriptions/${SUBSCRIPTION_ID}/resourcegroups/${NODE_RESOURCE_GROUP}"

完成結果如下,加兩個 AKS 叢集名稱-agentpool 的權限進去,順帶一提,如果有啟用 AGIC 和 ACI 功能,根據官方文件敘述,這兩個角色是擁有 節點資源群組 的參與者的權限的,所以當看到參與者的時候,不用覺得意外。

2020 11 29 14 45 05

安裝

在 AKS 安裝要多安裝 mic-exception.yaml,原因是因為,如果啟用 aad-pod-identity ,NMI ( Node Managed Identity - 節點受控身分識別 ) pod 會修改節點的 iptables,以攔截對 Azure IMDS 的呼叫。這項設定表示即使 pod 不使用 add-pod-identity,但因為已經啟用了,所以對 IMDS 所提出的任何要求都會被 NMI 攔截。您可以設定 CRD - AzurePodIdentityException,來通知 aad-pod-identity,任何在裡面定義的標籤,對 IMDS 的任何要求,都應該是被代理的,而不需要在 NMI 中處理。

而在 AzurePodIdentityException 會將 aks kube-system 裡面的 kubernetes.azure.com/managedby 來排除。

參考資料

Note : 底下是透過官方提供的 yaml 進行部署,預設會部署到 Default Namespace,若有需要,可以自行調整。 ( 小弟是改成部署到 aad-pod-identity namespace )

# 這邊針對有啟用 RBAC 進行安裝
kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.7.0/deploy/infra/deployment-rbac.yaml

# 如果是 AKS,請多安裝底下,因為必須允許 MIC 和 AKS add-ons 來存取 IMDS ( Azure Instance Metadata Service - 執行個體中繼資料服務 ) 而不會被 NMI ( Node Managed Identity ) 攔截
kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.7.0/deploy/infra/mic-exception.yaml

測試

接下來,我們要產生一個新的 Managed Identity,取名叫做 DEMO,並且把它放在 AKS 的節點資源群組裡面。( 小弟的節點資源群組名稱為 Study4Dev-JE-AKS-Study4Dev-JE-AKSResource,請注意,小弟這邊是自己重新定義了名稱,預設的名稱應該長的類似 MC${RESOURCEGROUP}${CLUSTERNAME}${CLUSTERLOCATION} )

而到時候,就會用這個 Managed Identity 來設定 Azure 資源的權限。

# 產生 Managed Identity
az identity create -g Study4Dev-JE-AKS-Study4Dev-JE-AKSResource -n DEMO

# 取得這個 MI 的 ID
az identity show -g Study4Dev-JE-AKS-Study4Dev-JE-AKSResource -n DEMO --query clientId -otsv

# 取得這個 Resource Group 的 ID
az identity show -g Study4Dev-JE-AKS-Study4Dev-JE-AKSResource -n DEMO --query id -otsv

# 最後,給予此 Managed Identity 讀取節點資源群組的權限
az role assignment create --role Reader --assignee DEMO --scope /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/Study4Dev-JE-AKS-Study4Dev-JE-AKSResource --query id -otsv

如果不想透過指令取得,也可以透過下圖取得 Managed Identity ID。

2020 11 30 15 51 21

同樣的,也可以取得 Managed Identity Resource ID。

2020 11 30 15 52 53

完成後的權限如下

2020 12 01 02 29 32

接下來,我們要產生 AzureIdentity 和 AzureIdentityBinding,透過這兩個 CRD,我們就可以提供 Pod 必要的權限。

  • 底下的 spec.type 0 代表的是 MSI, 1 代表的是 Service Principal,2 代表的是 使用 certificate 的 Service Principal
  • AzureIdentity.spec.resourceID 請填入 Managed Identity 的完整 ID
  • clientID 為 Managed Identity 的 ClientID
  • AzureIdentityBinding.spec.azureIdentity 必須對應到 AzureIdentity.metadata.name
  • AzureIdentityBinding.spec.selector 的值,未來的 Pod 裡面的 Label 需要對應上
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentity
metadata:
  name: demo
spec:
  type: 0
  resourceID: "/subscriptions/${SUBSCRIPTION_ID}/resourcegroups/Study4Dev-JE-AKS-Study4Dev-JE-AKSResource/providers/Microsoft.ManagedIdentity/userAssignedIdentities/DEMO"
  clientID: cb5569f2-8d09-4de1-a007-e02058e11d15
---
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentityBinding
metadata:
  name: demo-binding
spec:
  azureIdentity: demo
  selector: DEMO

這邊要注意幾點,第一,Labels.aadpodidbinding 必須匹配上面 AzureIdentityBinding 底下的 spec.selector 的值,所以這邊小弟故意用大寫的 DEMO。aad-pod-identity 會搜尋所有 Labels.aadpodidbinding 一樣的值,並且進行權限的分配。

至於 args 底下的參數,就只是這個 demo pod 的需求,請依據自己的狀況填入。

  • subscriptionid : 訂閱 ID
  • clientid : 上述產生之 Managed Identity ID
  • resourcegroup : 放置 Managed Identity 的 RG
apiVersion: v1
kind: Pod
metadata:
  name: demo
  labels:
    aadpodidbinding: DEMO
spec:
  containers:
  - name: demo
    image: mcr.microsoft.com/oss/azure/aad-pod-identity/demo:v1.7.0
    args:
      - --subscriptionid=${SUBSCRIPTION_ID}
      - --clientid=cb5569f2-8d09-4de1-a007-e02058e11d15
      - --resourcegroup=Study4Dev-JE-AKS-Study4Dev-JE-AKSResource
    env:
      - name: MY_POD_NAME
        valueFrom:
          fieldRef:
            fieldPath: metadata.name
      - name: MY_POD_NAMESPACE
        valueFrom:
          fieldRef:
            fieldPath: metadata.namespace
      - name: MY_POD_IP
        valueFrom:
          fieldRef:
            fieldPath: status.podIP
  nodeSelector:
    kubernetes.io/os: linux

成功後,可以看到 demo Pod 的 Log 成功的取得 MSI Token。

2020 11 30 16 08 11

番外篇 整合 ExternalDNS

其實,ExternalDNS 已經實作完,但還沒時間整成文章,而當初會想採用 AAD-Pod-Identity,其實一個目的,就是希望能讓 ExternalDNS 去控制 Azure DNS 的時候,不要走 SP,而改走 MI。

因為 ExternalDNS 文章還沒出來,所以小弟先塞這邊,未來 ExternalDNS 文章出來,我在移動過去。

如果想透過 AAD-Pod-Identity 給 ExternalDNS Pod MI,那步驟基本上和前面一樣。

我們要先產生 MI。

# 產生 Managed Identity
az identity create -g Study4Dev-JE-AKS-Study4Dev-JE-AKSResource -n External-DNS-AKS-Study4Dev-JE

# 取得這個 MI 的 ID
az identity show -g Study4Dev-JE-AKS-Study4Dev-JE-AKSResource -n External-DNS-AKS-Study4Dev-JE --query clientId -otsv

# 取得這個 Resource Group 的 ID
az identity show -g Study4Dev-JE-AKS-Study4Dev-JE-AKSResource -n External-DNS-AKS-Study4Dev-JE --query id -otsv

# 最後,給予此 Managed Identity 讀取節點資源群組的權限
az role assignment create --role Reader --assignee DEMO --scope /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/Study4Dev-JE-AKS-Study4Dev-JE-AKSResource --query id -otsv

完成後,也別忘記,要給 Azure DNS 那個群組讀取的權限,和 Azure DNS 參與者的權限如下。

Azure DNS 那個群組讀取的權限。 2020 12 01 16 53 27

Azure DNS 參與者的權限如下。

2020 12 01 16 54 58

然後同樣的,新增 AzureIdentity 和 AzureIdentityBinding。 ( Namespace 請自行決定位置 )

# 使用 AKS-Study4Dev-JE-agentpool MI 來存取 ACR
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentity
metadata:
  namespace: external-dns
  name: external-dns-access-azure-dns-identity
spec:
  type: 0
  resourceID: "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/Study4Dev-JE-AKS-Study4Dev-JE-AKSResource/providers/Microsoft.ManagedIdentity/userAssignedIdentities/External-DNS-AKS-Study4Dev-JE"
  clientID: ${MI_ClientID}
---
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentityBinding
metadata:
  namespace: external-dns
  name: external-dns-access-azure-dns-identity-binding
spec:
  azureIdentity: external-dns-access-azure-dns-identity
  selector: external-dns-access-azure-dns-identity

將 ExternalDNS 加上 Label

...
  labels:
    aadpodidbinding: external-dns-access-azure-dns-identity
...

最後修改 ExternalDNS 需要的 azure.conf

{
  "tenantId": "${tenantId}",
  "subscriptionId": "${SUBSCRIPTION_ID}",
  "resourceGroup": "${DNS 所在的 RG}",
  "useManagedIdentityExtension": true
}

就完成了~~~~

Troubleshooting

這邊附加上,最常用的 Troubleshooting 方式。

# 建立 az cli Pod,然後登入進去
kubectl run azure-cli -it --image=mcr.microsoft.com/azure-cli --labels=aadpodidbinding=<selector defined in AzureIdentityBinding> /bin/bash

# 透過 az login 查看是否能正常登入
az login -i --debug

後記

其實整個過程算簡單,但很遺憾的,目前類似 Flux、都還沒辦法直接與 Add-Pod-Identity 整合 ( Issue 直接被關掉 QQ ),但如果是自己撰寫的程式,想對外連接到 SQL Database 等等,還是一個很方便的做法。

最後,因 AAD-Pod-Identity 會使用 etc/kubernetes/azure.json,而這組 Service Principal 的權限比較大,這可能會造成,當可以控制 AAD-Pod-Identity 這個 Pod 的時候,就可以控制關於 AKS 的相關資源 ( 雖然小弟測試,sh 已經被拿掉..基本上算是安全的 )。但所以如果希望能將權限分離,可以參考官網的這篇,目前小弟還沒實作,未來如果有實作,會在更新。

參考資料

Sky & Study4.TW