Añadir usuario de Azure AD como administrador en una VM de Azure

Leyendo el título de esta entrada algunos dirán que porque me complico tanto la vida y no agrego a los usuarios como administradores de las VM mediante GPO si estas van a estar en dominio.

Pues bien, pongámonos en el caso que necesitamos agregar al responsable del despliegue de la VM como administrador (o a cualquier otro usuario) de manera automatizada y esta persona no debe tener permisos como admin local en ninguna otra VM que no sea esta.

La solución con la que di pese a que pueda parecer rebuscada, es la más sencilla de todas. Inyectar una extensión que incluya un custom script.

A continuación os muestro el proceso:

En primer lugar debemos generar y guardar un script que añadirá a un usuario como administrador local y que queremos añadir como extensión, en una Storage Account de Azure (En mi caso he creado un contenedor llamado Scripts dentro de la Storage Account y lo he guardado con el nombre de add_localAdmin.ps1). Si buscamos por Internet encontraremos cientos de ellos, como por ejemplo:

param(
[parameter(Mandatory=$true)]
 [String] $UPN
) 

$User = $UPN.split('@')[0]
$domain = $UPN.split('@')[-1]
$group = [ADSI]"WinNT://$ENV:COMPUTERNAME/Administrators,group"
$group.Add("WinNT://XxxDOMINIOxxX/$User,user") 

En este script se pasa por parámetro el User Principal Name ($UPN) de un usuario y este es añadido al grupo de Administradores locales del equipo (Este parámetro será proporcionado por el runbook que lo añadirá como extensión en la VM).

Seguidamente se debe crear el runbook  siguiente:

param(
 [parameter(Mandatory=$true)]
 [String] $Usermail,
 [parameter(Mandatory=$true)]
 [String] $vmname,
 [parameter(Mandatory=$true)]
 [String] $RGNAME
)
$connectionName = "AzureRunAsConnection"
try
{
 # Get the connection "AzureRunAsConnection "
 $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
 Add-AzureRmAccount `
 -ServicePrincipal `
 -TenantId $servicePrincipalConnection.TenantId `
 -ApplicationId $servicePrincipalConnection.ApplicationId `
 -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
 if (!$servicePrincipalConnection)
 {
 $ErrorMessage = "Connection $connectionName not found."
 throw $ErrorMessage
 } else{
 Write-Error -Message $_.Exception
 throw $_.Exception
 }
}
$StorageAccountName = "XxxNombreStorageAccountxxX"
$filename = "add_localAdmin.ps1"
$StorageAccountKey = Get-AzureRMStorageaccountKey -StorageAccountName $StorageAccountName -ResourceGroupName "XxxRGDeLaStorageAccountxxX"
Get-AzureRmVM -Name $vmname -ResourceGroupName $RGNAME | Set-AzureRmVMCustomScriptExtension -Name CustomScript -VMName $vmname -ResourceGroupName $RGNAME -StorageAccountName $StorageAccountName -StorageAccountKey $storageAccountKey.Key1 -ContainerName "scripts" -FileName $fileName -Run $fileName -Argument $usermail 

Donde podemos observar que se proporciona por parámetro el nombre de la VM sobre la que queremos añadir el usuario local ($vmname), el Resource group al que pertenece ($RGNAME) y el UPN del usuario en cuestión ($Usermail).

Seguidamente haciendo uso del CMDLET Get-AzureRMVM y combinándolo con  Set-AzureRmVMCustomScriptExtension introducimos como extensión el script que hemos alojado en nuestra Storage Account (add_localAdmin.ps1) y con el parámetro -Argument enviamos el usuario que queremos añadir como administrador.

Una vez ejecutado el runbook, ya tenemos un usuario de nuestro AzureAD agregado como Administrador local de una VM de Azure.

Decir que como mejora a este script le podríamos añadir una validación previa donde se comprueba si el usuario introducido existe o no en el Azure AD, pero esta comprobación debería ser lanzada con un usuario del dominio y no con una RunAsAccount.

$user = get-azurermaduser -Mail $usermail
if ($user -eq $null){
 write-output "-----> El usuario introducido NO existe en el AD"
 exit
} else{
 write-output "-----> El usuario $usermail introducido existe en el AD"
}

 

Programar Azure Runbooks desde Powershell ISE

Seguramente cuando vamos a programar un script en Azure Automation pensais que la interfaz disponible actualmente no es del todo cómoda, dado que en muchos casos programar desde el navegador puede dar algún que otro dolor de cabeza, ya que no es tan completo como el PowerShell Integrated Scripting Environment (ISE para los amigos).

integrationrunbooksise3

Pues bien, la solución es un Addon que ofrece el mismo ISE para poder crear, programar y testear runbooks directamente en nuestra suscripción de Azure. Para añadir esta funcionalidad debemos instalarla desde el mismo terminal con los siguientes comandos:

Install-Module AzureAutomationAuthoringToolkit -Scope CurrentUser
Install-AzureAutomationIseAddOn 

integrationrunbooksise

integrationrunbooksise4

Una vez importado el modulo debemos hacer sign in en nuestra suscripción, seleccionar la Automation Account pertinente y ya tendremos a nuestra disposición los runbooks y los Assets existentes.

En caso de cerrar y abrir de nuevo el ISE debemos importar de nuevo el modulo desde la linia de comando, o bien añadirlo desde la pestaña Addons:

 Import-Module AzureAutomationAuthoringToolkit 

Es importante recordar que cada vez que editemos un runbook o lo testemos debemos subirlo (Upload Draft) y publicarlo (Publish Draft) para que los cambios sean aplicados en la suscripción.

Despliegue de DevTest Labs en Azure

Para realizar el despliegue de un DevTest Lab en Azure, actualmente no existe ningún cmdlet que realice esta tarea, por lo que la deberemos realizar mediante el comando New-AzureRmResourceGroupDeployment del modo que se muestra a continuación, definiendo previamente la plantilla del DevTest Lab en un JSON.

New-AzureRmResourceGroupDeployment -Name deployNameDemoLab -ResourceGroupName $RG -Templateuri $TemplateuriX -TemplateParameterObject $Params -Verbose

Donde las variables son:

  • $RG: Nombre del Resource Group (String).
  • $TemplateuriX: variable que contiene la URI del JSON donde hemos definido los parámetros del DevTest Lab (String).
  • $Params: objeto previamente definido que inyecto al JSON (Objeto).
$params = @{}
$params.add("maxAllowedVmsPerUser",$NumVMUser)
$params.add("maxAllowedVmsPerLab",$NumVMLab)
$params.add("newLabName",$newLabName)

Es importante destacar porque realizo la inyección de un objeto $params en lugar de una URI hacia un JSON donde están definidos los parámetros que seria lo más común. El motivo es porque prefiero añadir los parámetros mediante un Asset definido en mi Automation Account, o bien, por código de Azure powershell/Azure automation.