Gestionar los usuarios inactivos en Azure AD con Microsoft Graph y Azure CLI

Una de las buenas prácticas a nivel de seguridad de las compañias es identificar de forma eficiente aquellos usuarios que llevan más de cierto tiempo sin conectarse y deshabilitar sus cuentas. En Azure Active Directory puedes gestionar este escenario utilizado Microsoft Graph. Hoy quiero contarte cómo puedes llevar a cabo estas consultas de manera sencilla con Azure CLI y aplicar las acciones que necesites.

Crear un service principal

Ya que se trata de una tarea propiamente de administración del directorio, y no quiero vincularla a una cuenta de usuario como tal, voy a generar un service principal para automatizar este procedimiento:

# Az login with an Azure AD administrator
az login
# Create a service principal
az ad sp create-for-rbac -n "ms-graph-client" --skip-assignment > auth.json

APP_ID=$(jq -r '.appId' auth.json)
APP_PASSWORD=$(jq -r '.password' auth.json)
TENANT_ID=$(jq -r '.tenant' auth.json)

Nota: en este ejemplo estoy usando la herramienta jq.

Asignar los permisos de Microsoft Graph al service principal

Para poder llevar a cabo las consultas que necesito sobre Microsoft Graph necesito los permisos, de tipo Application, AuditLog.Read.All, Directory.Read.All para conocer la actividad de los usuarios y User.ReadWrite.All para poder modificar la propiedad que deshabilita la cuenta:

# Add Microsoft Graph Permissions
# https://docs.microsoft.com/es-es/graph/permissions-reference#retrieving-permission-ids
# https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/howto-manage-inactive-user-accounts#what-permission-do-i-need-to-read-the-property
AUDIT_LOG_READ_ALL_APP_PERMISSION=$(az ad sp list --query "[?appDisplayName=='Microsoft Graph'].{permissions:appRoles}[0].permissions[?value=='AuditLog.Read.All'].id" --all | jq -r '.[]')
DIRECTORY_READ_ALL_APP_PERMISSION=$(az ad sp list --query "[?appDisplayName=='Microsoft Graph'].{permissions:appRoles}[0].permissions[?value=='Directory.Read.All'].id" --all | jq -r '.[]')
USER_READ_WRITE_APP_PERMISSION=$(az ad sp list --query "[?appDisplayName=='Microsoft Graph'].{permissions:appRoles}[0].permissions[?value=='User.ReadWrite.All'].id" --all | jq -r '.[]')
# Add permission to the service principal
az ad app permission add --id $APP_ID --api 00000003-0000-0000-c000-000000000000 --api-permissions $AUDIT_LOG_READ_ALL_APP_PERMISSION=Role $DIRECTORY_READ_ALL_APP_PERMISSION=Role $USER_READ_WRITE_APP_PERMISSION=Role 
# Grant permissions to the service principal
az ad app permission admin-consent --id $APP_ID
# check the permissions
az ad app permission list --id $APP_ID

Ten en cuenta que el proceso de consentir estos permisos puede tardar unos instantes en reflejarse. El resultado en el portal debería de ser el siguiente:

Resultado de los permisos asignados a través de Azure CLI

Iniciar sesión en Azure CLI con el service principal

Al igual que hice ayer en el artículo sobre cómo llamar a APIs protegidas con Azure AD usando Azure CLI, lo siguiente que necesitas hacer es iniciar sesión con este service principal que ya tiene los permisos adecuados:

#delete cached token
rm -f ~/.azure/msal_token_cache.json 
az login --service-principal -u $APP_ID -p $APP_PASSWORD --tenant $TENANT_ID --allow-no-subscriptions

Puedes comprobar el token que te está devolviendo con el siguiente comando:

# Get the access token you are using for this session
ACCESS_TOKEN=$(az account get-access-token --resource-type ms-graph | jq -r '.accessToken')
echo "http://jwt.ms/#access_token=$ACCESS_TOKEN"

y así validar que tiene los roles correctos, ya que de lo contrario las llamadas a Microsoft Graph no funcionará (posiblemente obtengas un 403).

Consultas a Microsoft Graph

Ahora ya puedes hacer las consultas que hemos venido a buscar. En la documentación que trata este asunto te recomiendan esta consulta para comprobar la última actividad de los usuarios:

# Retrieve all users, returning only their name and last activity
request_name_and_sigInActivity='https://graph.microsoft.com/beta/users?$select=displayName,signInActivity'
az rest --method get --url $request_name_and_sigInActivity | jq

y obtendrías un resultado como el siguiente:

Azure AD – Microsoft Graph – Nombre y última actividad

Sin embargo, lo que me interesa en este caso es recuperar aquellos que llevan más de 3 meses inactivos, y desactivar sus cuentas. Para ello podríamos utilizar una lógica como la siguiente:

# Recover only those users who have not logged in for more than three months
more_than_3_months_ago=$(date -v -3m +"%Y-%m-%dT%H:%M:%SZ")
request_users_3_months="https://graph.microsoft.com/beta/users?select=id,displayName,userPrincipalName,signInActivity,accountEnabled&\$filter=signInActivity/lastSignInDateTime le $more_than_3_months_ago"
az rest --method get --url $request_users_3_months > users.json
# Disable accounts
jq -r '.value[]|[.id, .displayName, .userPrincipalName, .signInActivity.lastSignInDateTime, .accountEnabled] | @tsv' users.json |
while IFS=$'\t' read -r id displayName userPrincipalName lastSignInDateTime accountEnabled; do
    
    #if upn is not null and account is enabled
    if [ -n "$userPrincipalName" ] && [ "$accountEnabled" = true ]; then
        echo "disabling the account with UPN $userPrincipalName"
        # Disable the account
        request_disable_user="https://graph.microsoft.com/v1.0/users/$id"        
        az rest --method patch --url $request_disable_user --body "{\"accountEnabled\": \"false\"}" 
    else
        echo "No UPN found for $id or the account is already disabled"
    fi   
done

Si quisieras posteriormente recuperar todas aquellas cuentas inactivas podrías usar esta otra consulta:

#Retrieve all disabled users
request_disabled_users="https://graph.microsoft.com/beta/users?select=id,displayName,userPrincipalName,signInActivity,accountEnabled&\$filter=accountEnabled eq false"
az rest --method get --url $request_disabled_users  > users_disabled.json

¡Saludos!