Azure - AKS - 使用 AAD Pod Identity 進行 Azure 服務驗證
前言
在 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 產生一筆。
而同樣的如果在開始建置的時候,和 ACR 進行關聯,底下就會看到和 ACRPull 的權限被加入進來。
如果想知道這個 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 ) 的數量等資訊。
而對於 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 功能,根據官方文件敘述,這兩個角色是擁有 節點資源群組 的參與者的權限的,所以當看到參與者的時候,不用覺得意外。
安裝
在 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。
同樣的,也可以取得 Managed Identity Resource ID。
完成後的權限如下
接下來,我們要產生 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。
番外篇 整合 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 參與者的權限如下。
然後同樣的,新增 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 已經被拿掉..基本上算是安全的 )。但所以如果希望能將權限分離,可以參考官網的這篇,目前小弟還沒實作,未來如果有實作,會在更新。
參考資料
- https://docs.microsoft.com/zh-tw/azure/aks/use-managed-identity
- https://github.com/Azure/aad-pod-identity/blob/master/hack/role-assignment.sh
- https://docs.microsoft.com/zh-tw/azure/aks/use-managed-identity
- https://itnext.io/using-aad-pod-identity-in-your-azure-kubernetes-clusters-what-to-watch-out-for-73d5d73960f