Hace unas semanas tuve que hacer algunas pruebas con la nueva integración de AKS con Azure Active Directory, donde en lugar usar los roles nativos de Kubernetes podemos abstraernos y trabajar directamente con roles de Azure AD para el mismo cometido. En este artículo te comparto mis pruebas.
Crear un clúster de AKS con RBAC con Azure AD
Si no tienes un clúster con Azure AD integrado para el RBAC puedes crearlo de la siguiente manera:
## Create an Azure AD group
GROUP_NAME="k8s-admins"
GROUP_DISYPLAY_NAME="Kubernetes Admins"
az ad group create --display-name $GROUP_DISYPLAY_NAME --mail-nickname $GROUP_NAME
GROUP_ID=$(az ad group show --group $GROUP_DISYPLAY_NAME --query objectId -o tsv)
## Create a cluster with Azure AD enabled
RESOURCE_GROUP="aks-with-aad"
AKS_NAME="aks-with-aad"
LOCATION="northeurope"
# Create a resource group
az group create \
--name $RESOURCE_GROUP \
--location $LOCATION
# Get tenant id
TENANT_ID=$(az account show --query tenantId -o tsv)
# Create AKS
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--node-count 1 \
--node-vm-size Standard_B4ms \
--generate-ssh-keys \
--enable-aad \
--aad-admin-group-object-ids $GROUP_ID \
--aad-tenant-id $TENANT_ID
En este caso creo un nuevo grupo de seguridad en mi tenant de Azure AD, llamado k8s-admins, que será el grupo de los administradores de este clúster. El resto de pruebas las haré directamente con usuarios, por simplicidad, pero te recomiendo encarecidamente que uses grupos en lugar de asignar roles directamente a los usuarios.
Para probar que funciona correctamente puedes añadir al usuario con el que has llevado a cabo esta operación a dicho grupo, recuperar las credenciales y ver que efectivamente funciona correctamente:
# Assign user to the AKS admin group
az ad group member add \
--group $GROUP_ID \
--member-id $(az ad signed-in-user show --query objectId -o tsv)
# Get users in an Azure AD group by display name
az ad group member list \
--group $GROUP_ID \
--query "[].{displayName:displayName,objectId:objectId}" \
-o table
# Access to the cluster
az aks get-credentials \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME
# Get nodes
kubectl get nodes
También puedes probar a eliminar dicho usuario y comprobar que efectivamente no tienes permisos para realizar la operación anterior, si vuelves a iniciar sesión:
# Remove user from the AKS admin group
az ad group member remove \
--group $GROUP_ID \
--member-id $(az ad signed-in-user show --query objectId -o tsv)
# Try again
# Access to the cluster
az aks get-credentials \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME
# Get nodes
kubectl get nodes # Forbidden
Roles predefinidos para AKS
Ahora que ya tienes un clúster vamos a ver cómo funciona ahora el tema de los roles. Por defecto, tenemos cuatro predefinidos que están listos para usar: Azure Kubernetes Service RBAC Reader, Azure Kubernetes Service RBAC Writer, Azure Kubernetes Service RBAC Admin y Azure Kubernetes Service RBAC Cluster Admin. Sus nombres son lo suficientemente descriptivos para saber lo que hacen cada uno de ellos 🙂 La forma de usarlo es súper sencilla: puedes asignarlo a un grupo/usuario (preferible lo primero) a un clúster o namespace dentro de este:
# Get AKS id
AKS_ID=$(az aks show --resource-group $RESOURCE_GROUP --name $AKS_NAME --query id -o tsv)
# Get user's object Id
JON_SNOW="$(az ad user list --query "[?displayName=='Jon Snow'].objectId" -o tsv)"
# Assign role at cluster level
az role assignment create \
--role "Azure Kubernetes Service RBAC Reader" \
--assignee $JON_SNOW --scope $AKS_ID
# Create a namespace
kubectl create ns the-north-remembers
# Assign role at namespace level
az role assignment create \
--role "Azure Kubernetes Service RBAC Writer" \
--assignee $JON_SNOW --scope $AKS_ID/the-north-remembers
Crear un rol personalizado
Si los roles que vienen por defecto no son suficientes para ti, la otra opción es crearlos de forma personalizada, como cualquier otro rol de Azure Active Directory. Para facilitarme el trabajo, siempre lo hago de la siguiente forma:
Lo primero, me descargo la definición de uno de los anteriores, por ejemplo Azure Kubernetes Service RBAC Writer:
# Get json file from a role definition called "Azure Kubernetes Service RBAC Writer"
az role definition list \
--query "[?roleName=='Azure Kubernetes Service RBAC Writer']" > standard-project-permissions.json
De esta forma, tengo una base con la que trabajar. Esto me devolverá algo como lo siguiente:
[
{
"assignableScopes": [
"/"
],
"description": "Allows read/write access to most objects in a namespace.This role does not allow viewing or modifying roles or role bindings. However, this role allows accessing Secrets and running Pods as any ServiceAccount in the namespace, so it can be used to gain the API access levels of any ServiceAccount in the namespace. Applying this role at cluster scope will give access across all namespaces.",
"id": "/subscriptions/<YOUR_SUBSCRIPTION_ID>/providers/Microsoft.Authorization/roleDefinitions/a7ffa36f-339b-4b5c-8bdf-e2c188b2c0eb",
"name": "a7ffa36f-339b-4b5c-8bdf-e2c188b2c0eb",
"permissions": [
{
"actions": [
"Microsoft.Authorization/*/read",
"Microsoft.Insights/alertRules/*",
"Microsoft.Resources/deployments/write",
"Microsoft.Resources/subscriptions/operationresults/read",
"Microsoft.Resources/subscriptions/read",
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Support/*"
],
"dataActions": [
"Microsoft.ContainerService/managedClusters/apps/controllerrevisions/read",
"Microsoft.ContainerService/managedClusters/apps/daemonsets/*",
"Microsoft.ContainerService/managedClusters/apps/deployments/*",
"Microsoft.ContainerService/managedClusters/apps/replicasets/*",
"Microsoft.ContainerService/managedClusters/apps/statefulsets/*",
"Microsoft.ContainerService/managedClusters/autoscaling/horizontalpodautoscalers/*",
"Microsoft.ContainerService/managedClusters/batch/cronjobs/*",
"Microsoft.ContainerService/managedClusters/batch/jobs/*",
"Microsoft.ContainerService/managedClusters/configmaps/*",
"Microsoft.ContainerService/managedClusters/endpoints/*",
"Microsoft.ContainerService/managedClusters/events.k8s.io/events/read",
"Microsoft.ContainerService/managedClusters/events/read",
"Microsoft.ContainerService/managedClusters/extensions/daemonsets/*",
"Microsoft.ContainerService/managedClusters/extensions/deployments/*",
"Microsoft.ContainerService/managedClusters/extensions/ingresses/*",
"Microsoft.ContainerService/managedClusters/extensions/networkpolicies/*",
"Microsoft.ContainerService/managedClusters/extensions/replicasets/*",
"Microsoft.ContainerService/managedClusters/limitranges/read",
"Microsoft.ContainerService/managedClusters/namespaces/read",
"Microsoft.ContainerService/managedClusters/networking.k8s.io/ingresses/*",
"Microsoft.ContainerService/managedClusters/networking.k8s.io/networkpolicies/*",
"Microsoft.ContainerService/managedClusters/persistentvolumeclaims/*",
"Microsoft.ContainerService/managedClusters/pods/*",
"Microsoft.ContainerService/managedClusters/policy/poddisruptionbudgets/*",
"Microsoft.ContainerService/managedClusters/replicationcontrollers/*",
"Microsoft.ContainerService/managedClusters/replicationcontrollers/*",
"Microsoft.ContainerService/managedClusters/resourcequotas/read",
"Microsoft.ContainerService/managedClusters/secrets/*",
"Microsoft.ContainerService/managedClusters/serviceaccounts/*",
"Microsoft.ContainerService/managedClusters/services/*"
],
"notActions": [],
"notDataActions": []
}
],
"roleName": "Azure Kubernetes Service RBAC Writer",
"roleType": "BuiltInRole",
"type": "Microsoft.Authorization/roleDefinitions"
}
]
En este caso ya sabes qué pinta tiene un rol que tiene permisos de escritura. Puedes leer más aquí sobre la definición de los roles. Lo siguiente es saber qué es lo que quieres que el rol permita hacer. Por ejemplo, necesito un rol que permita crear:
- Pods
- Services
- Deployments
- ConfigMaps
- Jobs y CronJobs
- StatefulSet
- Persistent Volume Claim
Por otro lado no quiero que los usuarios puedan crear, ni ver, Secrets, Namespaces, Storage Classes o Persistent Volumes.
Siguiendo estas directrices, puedo eliminar simplemente del anterior aquellos recursos que no estén en mi listado, quedando de la siguiente manera:
{
"assignableScopes": [
"/subscriptions/c65649e0-1113-4e90-b49f-9b4cb4f561d2"
],
"description": "Allows read/write access to Pods, Deployments, Services, Configmaps, Jobs, CronJobs, Statefulsets and Persistent Volume Claims.",
"name": "Standard Project Permissions",
"permissions": [{
"actions": [],
"dataActions": [
"Microsoft.ContainerService/managedClusters/apps/deployments/*",
"Microsoft.ContainerService/managedClusters/apps/replicasets/*",
"Microsoft.ContainerService/managedClusters/apps/statefulsets/*",
"Microsoft.ContainerService/managedClusters/batch/cronjobs/*",
"Microsoft.ContainerService/managedClusters/batch/jobs/*",
"Microsoft.ContainerService/managedClusters/configmaps/*",
"Microsoft.ContainerService/managedClusters/endpoints/*",
"Microsoft.ContainerService/managedClusters/events.k8s.io/events/read",
"Microsoft.ContainerService/managedClusters/events/read",
"Microsoft.ContainerService/managedClusters/extensions/deployments/*",
"Microsoft.ContainerService/managedClusters/extensions/replicasets/*",
"Microsoft.ContainerService/managedClusters/persistentvolumeclaims/*",
"Microsoft.ContainerService/managedClusters/pods/*",
"Microsoft.ContainerService/managedClusters/services/*"
],
"notActions": [],
"notDataActions": []
}]
}
Para crearlo en tu tenant basta con lanzar el siguiente comando:
# Create the new custom role
az role definition create \
--role-definition @standard-project-permissions.json
Puedes listar todos los roles personalizados en tu directorio fácilmente así:
# List custom roles
az role definition list \
--query "[?roleType=='CustomRole'].{roleName:roleName,roleId:name,roleType:roleType}" -o table
Y para asignarlo lo haces de la misma forma que uno de los roles predefinidos:
# Login as Daenerys Targaryen (admin)
az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME
# Create the namespace
NAMESPACE_NAME="demo-project"
kubectl create namespace $NAMESPACE_NAME
# Assign the custom role to the user
SANSA_STARK="$(az ad user list --query "[?displayName=='Sansa Stark'].objectId" -o tsv)"
# Assign the custom role to the user in the namespace
az role assignment create \
--role "Standard Project Permissions" \
--assignee $SANSA_STARK --scope $AKS_ID/namespaces/$NAMESPACE_NAME
Ahora, en mi ejemplo, Sansa Stark tiene los permisos asociados a mi rol personalizado. Para probar todo ello puedes usar las siguientes acciones:
###### Tests #########
# Login as Sansa Stark
az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME
# Create a pod in the namespace
kubectl run custom-role-demo-pod --image=nginx --restart=Never --namespace $NAMESPACE_NAME # Allow
kubectl get pod -n $NAMESPACE_NAME
# Try to create a pod in the default namespace
kubectl run custom-role-demo-pod --image=busybox --restart=Never # Forbidden
kubectl get pod # Forbidden
# Create a deployment in the namespace
kubectl create deployment custom-role-demo-deployment --image=nginx --namespace $NAMESPACE_NAME # Allow
kubectl get deploy # Forbidden
kubectl get deploy -n $NAMESPACE_NAME # Allow
kubectl describe deploy custom-role-demo-deployment -n $NAMESPACE_NAME # Allow
# Create a service in the namespace
kubectl expose deployment custom-role-demo-deployment --type=LoadBalancer --port 80 --namespace $NAMESPACE_NAME # Allow
kubectl get svc -n $NAMESPACE_NAME # Allow
kubectl get endpoints custom-role-demo-deployment -n $NAMESPACE_NAME # Allow
# Create configmap in the namespace
kubectl create configmap custom-role-demo-configmap --from-literal=key1=value1 --from-literal=key2=value2 --namespace $NAMESPACE_NAME # Allow
kubectl get configmap -n $NAMESPACE_NAME # Allow
kubectl create configmap custom-role-demo-configmap --from-literal=key1=value1 --from-literal=key2=value2 # Forbidden
# Create job in the namespace
kubectl create job custom-role-demo-job --namespace $NAMESPACE_NAME --image=busybox -- date # Allow
kubectl get job -n $NAMESPACE_NAME # Allow
# Create cronjob in the namespace
kubectl create cronjob custom-role-demo-cronjob --schedule="*/1 * * * *" --namespace $NAMESPACE_NAME --image=busybox -- date # Allow
kubectl get cronjob -n $NAMESPACE_NAME # Allow
# Create a persistent volume claim
kubectl apply -f sample-manifests/persistent-volume-claim.yaml --namespace $NAMESPACE_NAME # Allow
kubectl get pvc -n $NAMESPACE_NAME # Allow
# Create statefulset in the namespace
kubectl apply -f sample-manifests/statefulset.yaml --namespace $NAMESPACE_NAME # Allow
kubectl get statefulset -n $NAMESPACE_NAME # Allow
kubectl get pods -n $NAMESPACE_NAME -w # Allow
# Try to create secrets in the namespace
kubectl create secret generic custom-role-demo-secret --from-literal=key1=value1 --from-literal=key2=value2 --namespace $NAMESPACE_NAME # Forbidden
kubectl get secrets -n $NAMESPACE_NAME # Forbidden
# Try to create a namespace
kubectl create namespace custom-role-demo-namespace # Forbidden
kubectl get namespace # Forbidden
# Try to create a storage class
kubectl apply -f sample-manifests/storage-class.yaml # Forbidden
kubectl get storageclass # Forbidden
# Try to create a persistent volume
kubectl apply -f sample-manifests/persistent-volume.yaml # Forbidden
También puedes comprobar los diferentes permisos usando kubectl auth can-i <operación> <objeto>. Más información aquí.
¡Saludos!