Ejecutar remotamente un comando dentro de una máquina virtual en Azure

Hay ocasiones donde es necesario integrar procesos que tenemos corriendo en una máquina virtual con otros sistemas, y es posible que el hecho de estar dentro de una máquina virtual nos complique un poco el trabajo. En este artículo quiero mostrarte cómo ejecutar remotamente comandos dentro de una máquina virtual en Azure a través de la opción run command, sin necesidad de tener ningún puerto expuesto.

Comando de ejemplo

Para este ejemplo simplemente me he creado un archivo llamado c:\demo\test.bat con el siguiente contenido:

@ECHO OFF
ECHO Congratulations! Your batch file was executed successfully.

Más que suficiente para comprobar que todo funciona correctamente 😃

Probar tu comando desde el portal de Azure

Antes de entrar en harina, la forma rápida de probar que el comando ejecutado desde fuera funciona como esperas es a través de la sección Run command que tienes del recurso de tu máquina virtual:

Si todo va bien desde aquí puedes sentirte tranquilx de continuar. A partir de aquí tienes dos opciones para invocarlo remotamente: usando Azure CLI o directamente a través de la API REST.

Ejecutar el comando remotamente usando Azure CLI

Si desde donde necesitas ejecutar el comando es viable utilizar Azure CLI para su ejecución puedes conseguirlo a través del siguiente comando:

az vm run-command invoke --command-id RunPowerShellScript --name $VM_NAME -g $RESOURCE_GROUP_NAME --scripts "c:\demo\test.bat" --debug

En este caso la respuesta será la siguiente:

Resultado de la ejecución remota del comando con Azure CLI

Ejecutando el comando remotamente llamando a la API REST

En este caso la cosa se complica un poco, ya que el lugar donde tengamos que hacer esta llamada tiene que hacer varias cosas:

  1. Crear un service principal, que será el objeto que hará la llamada por nosotros.
  2. Asignar el rol de Virtual Machine Contributor a tu nuevo service principal.
  3. Recuperar el token con los permisos necesarios para poder hacer la petición.
  4. Generar la llamada HTTP que incluya el token anterior con el comando que queremos ejecutar.
  5. Verificar que el comando se ha ejecutado correctamente.
  1. Crear un service principal

Para poder hacer la llamada a través de la API REST lo primero que necesitas son unas credenciales que tengan los permisos necesarios. Para crearlo, puedes hacerlo fácilmente a través de estes comando:

# Create service principal
az ad sp create-for-rbac --name run-comand-demo-sp --skip-assignment > auth.json

# Get the client ID
CLIENT_ID=$(jq -r '.appId' auth.json)
CLIENT_SECRET=$(jq -r '.password' auth.json)

Sin embargo, este objeto todavía no tiene ningún rol o permiso asociado que nos permita hacer la ejecución del comando dentro de nuestra máquina virtual.

2. Asignar el rol Virtual Machine Contributor

Es por ello que vamos a asignarle el rol que necesitamos para poder ejecutar el comando remotamente. Tal y como indica la documentación este es el llamado Virtual Machine Contributor. Para asignarlo puedes hacerlo con este comando:

# Get subscription and tenant id
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
TENANT_ID=$(az account show --query tenantId -o tsv)

#Assign Virtual Machine Contributor role to the service principal
az role assignment create --role "Virtual Machine Contributor" --assignee $CLIENT_ID --scope /subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Compute/virtualMachines/$VM_NAME

También puedes hacerlo a través del portal de Azure, en el apartado Access Control (IAM), quedando de la siguiente forma:

Virtual Machine Contributor asignado al service principal que hará la llamada

Recuperar el token

En este caso, voy a utilizar el flujo de OAuth 2.0 Client Credentials, ya que la llamada la vamos a realizar machine to machine:

# Get token using cURL client credential flow
TOKEN=$(curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&resource=https%3A%2F%2Fmanagement.core.windows.net%2F" "https://login.microsoftonline.com/$TENANT_ID/oauth2/token" | jq -r '.access_token')

Este token tendrá un tiempo de expiración que te permitirá hacer múltiples llamadas hasta que el mismo expire.

Hacer la llamada

Lo último que nos queda es hacer la llamada donde solicitamos la ejecución del comando, en este caso de nuestro archivo c:\demo\test.bat. Por simplificarlo mucho, en este ejemplo utilizo cURL para hacer esta tarea:

# Invoke the command using the token
curl -X POST -D - -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"commandId":"RunPowerShellScript", "script": ["c:\\demo\\test.bat"]}' "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Compute/virtualMachines/$VM_NAME/runCommand?api-version=2021-07-01"

En el caso de que no hayas aplicado el rol Virtual Machine Contributor obtendrás un error al no tener permisos:

Error al no tener los permisos suficientes para llevar a cabo esta tarea

Por otro lado, si todo ha ido bien lo que tendrás es una respuesta sin body, pero si unas cabeceras donde te proporcionará la URL de seguimiento de la operación, bajo el nombre de azure-asyncoperation, ya que se está ejecutando de manera asíncrona:

azure-asyncoperation

Si compruebas esta a través de un GET con cURL:

# Check the status of the command
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" https://<YOUR_AZURE_ASYNCOPERATION_URL>

Obtendrás el resultado de la ejecución de tu comando:

Resultado de la operación

¡Saludos!