SMS Blog
Use Azure Automation Runbook to deploy Nessus Agent via Terraform
Problem
All Virtual Machines (VMs) in the Azure environment must have Nessus Agent installed and registered to a newly created Nessus Manager without direct SSH or RDP access to any of the VMs.
Solution
Use an existing Azure Automation Account to deploy the Nessus Agent via a runbook. The runbook will add a Virtual Machine extension that will have the necessary steps to install and register the Nessus Agent based on the Operating System. This solution can be used to install pretty much anything on a Windows or Linux Virtual Machine.
What is an Azure Automation Account?
An Azure Automation Account is a cloud-based management service provided by Microsoft, designed to help automate, orchestrate, and manage repetitive tasks and processes within the Azure environment. It serves as a centralized location for storing various automation assets, such as runbooks, credentials, and integration modules, enabling users to streamline their automation efforts and improve operational efficiency.
In this case, an existing Azure Automation Account that was previously created is being used for this effort. If you don’t have an existing one, you can create a new one strictly for this purpose. There are a couple of requirements that are needed to make this work.
- Associate a User-assigned Managed Identity with at a minimum, the “Virtual Machine Contributor” Azure role to all subscriptions in your tenant.
- The Azure Automation Account must be linked to the same Log Analytics workspace that your VMs are linked. In this environment this task was previously taken care of to accomplish another effort. To associate VMs to a Log Analytics workspace, you will need the OMS or the MMA agent. See the link below as there are many ways to tackle this.
As mentioned above, if you don’t already have an Automation Account, you will need to create one. Below is an example of creating an Azure Automation Account with Terraform.
resource "azurerm_automation_account" "aa_account" { location = "<azure region>" name = "<name of account>" resource_group_name = var.rg identity { identity_ids = ["<Your Managed identity ids>"] type = "UserAssigned }
What is an Azure Automation Runbook?
An Azure Automation Runbook is a set of tasks or operations that you can automate within the Azure environment. It is essentially a collection of PowerShell or Python script(s) that perform various actions, such as managing resources, configuring systems, or handling other operational tasks. Azure Automation Runbooks are commonly used for automating repetitive tasks, scheduling maintenance activities, and orchestrating complex workflows within Azure.
PowerShell 5.x was the scripting language used for this task, in part because Terraform does not currently support Powershell 7.1 as a runbook type. (e.g. https://github.com/hashicorp/terraform-provider-azurerm/issues/14089).
Terraform
Terraform is the current Infrastructure As Code tool for this environment therefore it used in this scenario. Below is a snippet of the main.tf
Let’s take a look at the Terraform code:
resource "azurerm_automation_runbook" "nessus_install" { name = var.runbook_name location = data.azurerm_resource_group.ops.location resource_group_name = data.azurerm_automation_account.ops.resource_group_name automation_account_name = data.azurerm_automation_account.ops.name log_verbose = true log_progress = true description = var.runbook_description runbook_type = var.runbook_type tags = var.default_tags content = templatefile("${path.module}/runbook/nessus.ps1", { umi = data.azurerm_user_assigned_identity.identity.client_id tenantid = var.tenant_id scriptnamelinux = var.scritpname_linux scriptnamewindows = var.scritpname_win storageaccountcontainer = data.azurerm_storage_container.sa.name storageaccountresourcegroup = data.azurerm_resource_group.sa.name storageaccountname = var.sa_acct workbookname = var.runbook_name storageaccountsub = data.azurerm_subscription.sa.subscription_id client_id = data.azurerm_user_assigned_identity.identity.client_id vms_to_exclude = join(",", [for vm in local.vms_file_content : "\"${vm}\""]) defaultsub = "" }) } resource "azurerm_automation_job_schedule" "nessus_install" { resource_group_name = data.azurerm_automation_account.ops.resource_group_name automation_account_name = data.azurerm_automation_account.ops.name schedule_name = azurerm_automation_schedule.nessus_install.name runbook_name = azurerm_automation_runbook.nessus_install.name } resource "azurerm_automation_schedule" "nessus_install" { name = var.nessus_schedule resource_group_name = data.azurerm_automation_account.ops.resource_group_name automation_account_name = data.azurerm_automation_account.ops.name frequency = var.schedule_frequency timezone = var.timezone start_time = var.start_time description = var.schedule_description week_days = var.week_days expiry_time = var.expiry_time }
azurerm_automation_runbook: This section defines the Azure Automation Runbook, including its name, location, resource group, and related configurations. The templatefile is using several inputs that allow you to modify your variables and have your script configured with the desired output. The script utilizes a PowerShell script file named `nessus.ps1`, which is responsible for orchestrating the Nessus installation process and covered in the next section.
azurerm_automation_job_schedule: Here, we set up an Azure Automation Job Schedule, which determines the frequency and timing of the execution of the Nessus installation process.
azurerm_automation_schedule: This section specifies the details of the schedule, including the frequency, time zone, start time, and expiry time for the Nessus installation process. This needs to be run on a weekly basis to incorporate any new VMs that get created in any subscription.
If you choose to use the code as-is, the variables used in the templatefile are explained below.
umi = User Managed Identity that is associated with the Azure Automation Account tenantid = The Tenant ID scriptnamelinux = Name of Linux shell script scriptnamewindows = Name of Windows script storageaccountcontainer = Name of the Storage Account where the scripts reside storageaccountresourcegroup = Name of the Resource Group where the Storage Account resides storageaccountname = Name of the Storage Account workbookname = Name of the Runbook you are creating storageaccountsub = The Subscription ID of the Storage Account vms_to_exclude = join(",", [for vm in local.vms_file_content : "\"${vm}\""]) defaultsub = "" # If you want to loop through all active subscriptions leave this as-is, if not put in the subscription you want to this script to run against
vms_to_exclude variable was configured so you can skip VMs by name if you choose. An issue occurred where a VM’s resources were pegged and the script would eventually error out waiting for the VM to finish. So this logic was inserted to mitigate that. A flat txt file “vms.txt” is used for this purpose, you can just list all VMs in this file, one per line.
Powershell
Let’s take a look at the Powershell script that is being called nessus.ps1
Disable-AzContextAutosave -Scope Process $AzureContext = (Connect-AzAccount -Identity -Environment AzureUSGovernment -AccountId ${umi}).context $TenantId = '${tenantid}' $scriptNameLinux = '${scriptnamelinux}' $scriptNameWindows = '${scriptnamewindows}' $storageAccountContainer = '${storageaccountcontainer}' $storageAccountResourceGroup = '${storageaccountresourcegroup}' $storageAccountName = '${storageaccountname}' $defaultSubscriptionId = '${defaultsub}' $settingsLinux = @{ "fileUris" = @("https://$storageAccountName.blob.core.usgovcloudapi.net/$storageAccountContainer/$scriptNameLinux") "commandToExecute" = "bash $scriptNameLinux" } | ConvertTo-Json $settingsWindows = @{ "fileUris" = @("https://$storageAccountName.blob.core.usgovcloudapi.net/$storageAccountContainer/$scriptNameWindows") "commandToExecute" = "powershell -NonInteractive -ExecutionPolicy Unrestricted -File $scriptNameWindows" } | ConvertTo-Json $storageKey = (Get-AzStorageAccountKey -Name $storageAccountName -ResourceGroupName $storageAccountResourceGroup)[0].Value $protectedSettingsLinux = @{ "storageAccountName" = $storageAccountName "storageAccountKey" = $storageKey } | ConvertTo-Json $protectedSettingsWindows = @{ "storageAccountName" = $storageAccountName "storageAccountKey" = $storageKey } | ConvertTo-Json $currentAZContext = Get-AzContext if ($currentAZContext.Tenant.id -ne $TenantId) { Write-Output "This script is not authenticated to the needed tenant. Running authentication." Connect-AzAccount -TenantId $TenantId } else { Write-Output "This script is already authenticated to the needed tenant - reusing authentication." } $subs = @() if ($defaultSubscriptionId -eq "") { $subs = Get-AzSubscription -TenantId $TenantId | Where-Object { $_.State -eq "Enabled" } } else { if ($defaultSubscriptionId.IndexOf(',') -eq -1) { $subs = Get-AzSubscription -TenantId $TenantId -SubscriptionId $defaultSubscriptionId } else { $defaultSubscriptionId = $defaultSubscriptionId -replace '\s', '' $subsArray = $defaultSubscriptionId -split "," foreach ($subsArrayElement in $subsArray) { $currTempSub = Get-AzSubscription -TenantId $TenantId -SubscriptionId $subsArrayElement $subs += $currTempSub } } } $excludeVmNamesArray = (${vms_to_exclude}) foreach ($currSub in $subs) { Set-AzContext -subscriptionId $currSub.id -Tenant $TenantId if (!$?) { Write-Output "Error occurred during Set-AzContext. Error message: $( $error[0].Exception.InnerException.Message )" Write-Output "Trying to disconnect and reconnect." Disconnect-AzAccount Connect-AzAccount -TenantId $TenantId -SubscriptionId $currSub.id Set-AzContext -subscriptionId $currSub.id -Tenant $TenantId } $VMs = Get-AzVM foreach ($vm in $VMs) { if ($excludeVmNamesArray -contains $vm.Name) { Write-Output "Skipping VM $($vm.Name) as it is excluded." continue } $status = (Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status).Statuses[1].DisplayStatus if ($status -eq "VM running") { Write-Output "Processing running VM $( $vm.Name )" $extensions = (Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name).Extensions foreach ($ext in $extensions) { if ($null -ne $vm.OSProfile.WindowsConfiguration) { if ($ext.VirtualMachineExtensionType -eq "CustomScriptExtension") { Write-Output "Removing CustomScriptExtension with name $( $ext.Name ) from VM $( $vm.Name )" Remove-AzVMExtension -ResourceGroupName $vm.ResourceGroupName -VMName $vm.Name -Name $ext.Name -Force Write-Output "Removed CustomScriptExtension with name $( $ext.Name ) from VM $( $vm.Name )" } } else { if ($ext.VirtualMachineExtensionType -eq "CustomScript") { Write-Output "Removing CustomScript extension with name $( $ext.Name ) from VM $( $vm.Name )" Remove-AzVMExtension -ResourceGroupName $vm.ResourceGroupName -VMName $vm.Name -Name $ext.Name -Force Write-Output "Removed CustomScript extension with name $( $ext.Name ) from VM $( $vm.Name )" } } } if ($vm.StorageProfile.OsDisk.OsType -eq "Windows") { Write-Output "Windows VM detected: $( $vm.Name )" $settingsOS = $settingsWindows $protectedSettingsOS = $protectedSettingsWindows $publisher = "Microsoft.Compute" $extensionType = "CustomScriptExtension" $typeHandlerVersion = "1.10" } elseif ($vm.StorageProfile.OsDisk.OsType -eq "Linux") { Write-Output "Linux VM detected: $( $vm.Name )" $settingsOS = $settingsLinux $protectedSettingsOS = $protectedSettingsLinux $publisher = "Microsoft.Azure.Extensions" $extensionType = "CustomScript" $typeHandlerVersion = "2.1" } $customScriptExtensionName = "NessusInstall" Write-Output "$customScriptExtensionName installation on VM $( $vm.Name )" Set-AzVMExtension -ResourceGroupName $vm.ResourceGroupName ` -Location $vm.Location ` -VMName $vm.Name ` -Name $customScriptExtensionName ` -Publisher $publisher ` -ExtensionType $extensionType ` -TypeHandlerVersion $typeHandlerVersion ` -SettingString $settingsOS ` -ProtectedSettingString $protectedSettingsOS Write-Output "---------------------------" } else { Write-Output "VM $( $vm.Name ) is not running, skipping..." } } Set-AzContext -SubscriptionId $defaultSubscriptionId -Tenant $TenantId }
This particular environment is in the AzureGOV region but could be modified to use any region.
The script is designed to automate the deployment of custom scripts/extensions to multiple Azure VMs across different subscriptions. It provides flexibility for both Linux and Windows VMs and ensures that any existing custom script extensions are removed before deployment; this is because you cannot have an extension with the same name.
OS Scripts
Now lets look at the windows script that the “nessus.ps1” calls
$installerUrl = "<URL to the msi>" $NESSUS_GROUP="<Name of your Nessus Group>" $NESSUS_KEY="<Name of Nessus Key>" $NESSUS_SERVER="<FQDN of Nessus Server>" $NESSUS_PORT="<Port if different from standard 8834>" $installerPath = "C:\TEMP\nessusagent.msi" $windows_package_name = "'Nessus Agent (x64)'" $installed = Get-WmiObject -Query "SELECT * FROM Win32_Product WHERE Name = $windows_package_name" | Select-Object Name function Test-Admin { $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent()) $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } if ((Test-Admin) -eq $false) { if ($elevated) { } else { Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition)) } exit } 'running with full privileges' if ($installed) { Write-Output "Nessus Agent is already installed. Exiting." } else { Write-Output "Downloading Nessus Agent MSI installer..." Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath Write-Output "Installing Nessus Agent..." Start-Process -FilePath msiexec.exe -ArgumentList '/i C:\TEMP\nessusagent.msi NESSUS_GROUPS="$NESSUS_GROUP" NESSUS_SERVER="$NESSUS_SERVER" NESSUS_KEY='$NESSUS_KEY' /qn' -Wait $installed = Get-WmiObject -Query "SELECT * FROM Win32_Product WHERE Name = $windows_package_name" | Select-Object Name if ($installed) { Write-Output "Nessus Agent has been successfully installed." } else { Write-Output "Failed to install Nessus Agent." } } if (Test-Path $installerPath) { Remove-Item -Path $installerPath -Force } Function Start-ProcessGetStream { [CmdLetBinding()] Param( [System.IO.FileInfo]$FilePath, [string[]]$ArgumentList ) $pInfo = New-Object System.Diagnostics.ProcessStartInfo $pInfo.FileName = $FilePath $pInfo.Arguments = $ArgumentList $pInfo.RedirectStandardError = $true $pInfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pInfo.CreateNoWindow = $true $pInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $proc = New-Object System.Diagnostics.Process $proc.StartInfo = $pInfo Write-Verbose "Starting $FilePath" $proc.Start() | Out-Null Write-Verbose "Waiting for $($FilePath.BaseName) to complete" $proc.WaitForExit() $stdOut = $proc.StandardOutput.ReadToEnd() $stdErr = $proc.StandardError.ReadToEnd() $exitCode = $proc.ExitCode Write-Verbose "Standard Output: $stdOut" Write-Verbose "Standard Error: $stdErr" Write-Verbose "Exit Code: $exitCode" [PSCustomObject]@{ "StdOut" = $stdOut "Stderr" = $stdErr "ExitCode" = $exitCode } } Function Get-NessusStatsFromStdOut { Param( [string]$stdOut ) $stats = @{} $StdOut -split "`r`n" | ForEach-Object { if ($_ -like "*:*") { $result = $_ -split ":" $stats.add(($result[0].Trim() -replace "[^A-Za-z0-9]", "_").ToLower(), $result[1].Trim()) } } Return $stats } Function Get-DateFromEpochSecond { Param( [int]$seconds ) $utcTime = (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($seconds)) Return Get-Date $utcTime.ToLocalTime() -Format "yyyy-MM-dd HH:mm:ss" } Try { $nessusExe = Join-Path $env:ProgramFiles -ChildPath "Tenable\Nessus Agent\nessuscli.exe" -ErrorAction Continue } Catch { Throw "Cannot find NessusCli.exe, installing..." } Write-Output "Getting Agent Status..." $agentStatus = Start-ProcessGetStreams -FilePath $nessusExe -ArgumentList "agent status" If ($agentStatus.stdOut -eq "" -and $agentStatus.StdErr -eq "") { Throw "No Data Returned from NessusCli, linking now" Start-ProcessGetStreams -FilePath $nessusExe -ArgumentList 'agent link --key=$NESSUS_KEY --groups="$NESSUS_GROUP" --host=$NESSUS_SERVER --port=$NESSUS_PORT' } elseif ($agentStatus.StdOut -eq "" -and $agentStatus.StdErr -ne "") { Throw "StdErr: $($agentStatus.StdErr)" } elseif (-not($agentStatus.stdOut -like "*Running: *")) { Throw "StdOut: $($agentStatus.StdOut)" } else { $stats = Get-NessusStatsFromStdOut -stdOut $agentStatus.StdOut If ($stats.linked_to -eq '$NESSUS_SERVER' -and $stats.link_status -ne 'Not linked to a manager') { Write-Output "Connected to $NESSUS_SERVER" } else { Write-Output "Connecting..." Start-ProcessGetStreams -FilePath "C:\Program Files\Tenable\Nessus Agent\nessuscli.exe" -ArgumentList 'agent link --key=$NESSUS_KEY --groups="$NESSUS_GROUP" --host=$NESSUS_SERVER --port=$NESSUS_PORT' } If ($stats.last_connection_attempt -as [int]) { $stats.last_connection_attempt = Get-DateFromEpochSeconds $stats.last_connection_attempt } If ($stats.last_connect -as [int]) { $stats.last_connect = Get-DateFromEpochSeconds $stats.last_connect } If ($stats.last_scanned -as [int]) { $stats.last_connect = Get-DateFromEpochSeconds $stats.last_scanned } } #$stats | Out-Host
This script streamlines the process of installing and linking the Nessus Agent to the specified Nessus server, automating various steps and ensuring the seamless deployment and integration of the agent within the intended environment.
Now lets look at the linux script that the “nessus.ps1” calls:
#!/bin/bash exec 3>&1 4>&2 trap 'exec 2>&4 1>&3' 0 1 2 3 exec 1>/tmp/nessus-install-log.out 2>&1 PACKAGE_NAME="nessusagent" ACTIVATION_CODE="<Your Nessus Activation Key/Code>" NESSUS_HOST="<fqdn of your Nessus Manager>" NESSUS_AGENT="/opt/nessus_agent/sbin/nessuscli" NESSUS_PORT="<port # if different from 8834>" NESSUS_GROUP="<name of your group>" base_url="<url to your Storage Account>" debian_filename="NessusAgent-10.3.1-ubuntu1404_amd64.deb" # redhat_7_filename="NessusAgent-10.3.1-es7.x86_64.rpm" # Redhat EL7 filename redhat_8_filename="NessusAgent-10.3.1-es8.x86_64.rpm" # Redhat EL8 filename if_register_agent() { if "$NESSUS_AGENT" agent status | grep -q "Linked to: $NESSUS_HOST"; then echo "Nessus Agent is already linked to Nessus Manager." else $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP" if [ $? -eq 0 ]; then echo "Nessus Agent linked successfully." else echo "Failed to link Nessus Agent. Check your activation code or permissions." exit 1 fi fi } is_package_installed_debian() { if dpkg -l | grep -i "ii $PACKAGE_NAME"; then if_register_agent return 0 else return 1 fi } is_package_installed_redhat() { if rpm -qa | grep -i "$PACKAGE_NAME" > /dev/null; then if_register_agent return 0 else return 1 fi } install_package_debian() { echo "$PACKAGE_NAME is not installed on $ID. Installing it now..." && sleep 20 && wget -qP /tmp $base_url$debian_filename && sleep 20 && dpkg -i /tmp/"$debian_filename" && sleep 20 && $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP" && sleep 20 && systemctl enable nessusagent --now && sleep 20 && $NESSUS_AGENT agent status | tee /tmp/nessus_agent_status && sleep 20 && rm -f /tmp/"$debian_filename" exit } install_package_redhat_v7() { echo "$PACKAGE_NAME is not installed on $ID-$VERSION_ID Installing it now..." yum -y install wget && sleep 20 && wget -qP /tmp $base_url$redhat_7_filename && sleep 20 && rpm -ivh /tmp/"$redhat_7_filename" && sleep 20 && $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP" && sleep 20 && systemctl enable nessusagent --now && sleep 20 && $NESSUS_AGENT agent status | tee /tmp/nessus_agent_status && rm -f /tmp/"$redhat_7_filename" exit } install_package_redhat_v8() { echo "$PACKAGE_NAME is not installed on $ID-$VERSION_ID. Installing it now..." sleep 20 && wget -qP /tmp $base_url$redhat_8_filename && sleep 20 && rpm -ivh /tmp/"$redhat_8_filename" && sleep 20 && $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP" && sleep 20 && systemctl enable nessusagent --now && sleep 20 && $NESSUS_AGENT agent status | tee /tmp/nessus_agent_status && rm -f /tmp/"$redhat_8_filename" exit } check_debian_based() { lowercase_id=$(echo "$ID" | tr '[:upper:]' '[:lower:]') if [[ "$lowercase_id" == *debian* || "$lowercase_id" == *ubuntu* ]]; then if is_package_installed_debian; then echo "$PACKAGE_NAME is already installed on $ID." exit 0 else install_package_debian fi fi } check_redhat_based() { lowercase_id=$(echo "$ID" | tr '[:upper:]' '[:lower:]') if [[ "$lowercase_id" == *centos* || "$lowercase_id" == *rhel* || "$lowercase_id" == *ol* || "$lowercase_id" == *el* ]]; then if is_package_installed_redhat; then echo "$PACKAGE_NAME is already installed on $ID." exit 0 else if [[ "$VERSION_ID" == 7 ]]; then echo "Red Hat $ID version 7 detected." install_package_redhat_v7 elif [[ "$VERSION_ID" == 8 ]]; then echo "Red Hat $ID version 8 detected." install_package_redhat_v8 else echo "Unsupported version: $VERSION_ID" exit 1 fi fi fi } if [ -f /etc/os-release ]; then . /etc/os-release check_debian_based check_redhat_based else echo "Unsupported Linux distribution." exit 1 fi
This script is pretty much the same as the one above except it is for linux distributions. It will determine the OS type and install the necessary agent package and register the agent to the appropriate Nessus Manager.
Example of the variables.tf
variable "default_tags" { description = "A map of tags to add to all resources" type = map(string) default = { } } variable "tenant_id" { description = "Azure AD Tenate ID of the Azure subscription" type = string } variable "nessus_schedule" { description = "Name of the Schedule in Automation Account" type = string default = "nessus-automation-schedule" } variable "timezone" { description = "Name of the Timezone" type = string default = "America/New_York" } variable "schedule_description" { description = "Schedule Description" type = string default = "This is schedule to download and install Nessus" } variable "week_days" { description = "Schedule Description" type = list(string) default = ["Monday", "Wednesday", "Saturday"] } variable "scritpname_linux" { default = "nessus-linux.sh" description = "Name of Linux script" type = string } variable "scritpname_win" { default = "nessus-windows.ps1" description = "Name of Windows script" type = string } variable "sa_container" { description = "Name of the Storage Account Container" type = string } variable "sa_rg" { description = "Name of the Storage Account Resource Group" type = string } variable "sa_sub" { description = "Subscription ID where the Storage Account lives" type = string } variable "sa_acct" { description = "Name of the Storage Account" type = string } locals { vms_file_content = split("\n", file("${path.module}/vms.txt")) } variable "schedule_frequency" { description = "Job frequency" type = string default = "Week" } variable "runbook_name" { description = "Name of the runbook" type = string default = "nessus_agent_install" } variable "runbook_type" { description = "Name of the language used" type = string default = "PowerShell" } variable "runbook_description" { description = "Description of the Runbook" type = string default = "This runbook will Download and Install the Nessus Agent" } variable "start_time" { description = "When to start the runbook schedule" type = string default = "2024-10-07T06:00:15+02:00" } variable "expiry_time" { description = "When to start the runbook schedule" type = string default = "2027-10-07T06:00:15+02:00" } variable "identity_sub" { description = "Subscription where MI lives" type = string }
All the above code can be found at the link below.