Pages

Showing posts with label Azure VM. Show all posts
Showing posts with label Azure VM. Show all posts

Saturday, June 10, 2023

Attempting to join a Windows desktop to a Active Directory Domain Services (AD DS) fails with: "The following error occurred attempting to join the domain contoso.local": The specified network name is no longer available.

One of the projects I’ve been working on was a small Azure Virtual Desktop deployment for resources outside of Canada to securely access a VDI in Azure’s Canada Central region. To provide a “block all traffic and only allow whitelisted domain” solution, I opted to use the new Azure Firewall Basic SKU with Application Rules. Given there wasn’t any ingress traffic originating from the internet for published applications and connectivity to the AVDs were going to be through Microsoft’s managed gateway, I decided to place the Azure Firewall in the same VNet as the virtual desktops and servers. This doesn’t conform to the usual hub and spoke topology and the main reason for this is to avoid VNet to VNet peering costs between the subnets. What I have elected for the security network design was to send all traffic between the subnets within the same VNet through the firewall for visibility and logging so the default of traffic free flowing within the same VNet is not allowed. The following is a diagram of the topology:

image

The traffic originating from the AVD subnet containing the virtual desktops to the server subnet containing the AD DS servers are protected by the firewall. After placing the required route in the UDR associated to the AVD subnet and configuring the required firewall ports from client to server in the Network rules of the firewall policy:

  • UDP Port 88 for Kerberos authentication.
  • UDP and TCP Port 135 for the client to domain controller operations and domain controllers to domain controller operations.
  • TCP Port 139 and UDP 138 are used for File Replication Service between domain controllers.
  • UDP Port 389 for LDAP to handle regular queries from client computers to domain controllers.
  • TCP and UDP Port 445 for File Replication Service.
  • TCP and UDP Port 464 for Kerberos Password Change.
  • TCP Port 3268 and 3269 for Global Catalog from client to domain controller.
  • TCP and UDP Port 53 for DNS from domain controller to domain controller and client to the domain controller.
image

… then proceeding to deploy the desktops with AVD, it would fail to join the desktop to the domain with the error message:

VM has reported a failure when processing extension 'joindomain'. Error message: "Exception(s) occurred while joining Domain contoso.local

Trying to manually join the desktops to the domain will display the following message:

"The following error occurred attempting to join the domain contoso.local": The specified network name is no longer available.

image

Parsing through the logs of the Azure Firewall did not reveal any Deny activity but I did notice that there wasn’t any return traffic captured. It was then that I found I had forgotten to associate the UDR that would force traffic from the server subnet to the VDI subnet through the firewall.

image

This meant that any traffic originating from the VDI subnet would be sent through the firewall:

image

… while any traffic originating from the server subnet to the VDI subnet would just be sent through subnet to subnet within the same VNet. I’m not completely sure why this would be a problem given return traffic should have returned through the firewall and only new traffic from the domain controllers would not.

In any case, I went ahead and updated the server subnet to use the UDR that would route the traffic through the firewall and the domain join operation succeeded. Firewall logs would also began displaying the domain communication traffic to the AVD subnet.

This probably would have been resolved when I completed the configuration but I hope this blog post would help anyone who may encounter a similar issue.

Monday, February 28, 2022

Using an Automation Account to monitor a VM's Windows service and a Runbook to use Invoke-AzVMRunCommand to restart a stopped service

In my previous post:

Using Azure Change Tracking and Inventory to monitor Windows Services
http://terenceluk.blogspot.com/2022/02/using-azure-change-tracking-and.html

I demonstrated how to set up Change Tracking and Inventory in Azure Automation to monitor Windows services in a virtual machine and alert when the service was no longer in a running state. With monitoring and alerting in place, the next step is to incorporate automation so that issues can be immediately remediated without requiring manual intervention. Azure provides multiple methods for automation and for this post, I will demonstrate how to achieve this with the following:

To avoid recreating the same content that demonstrate how to set up monitoring for a virtual machine’s Windows service, let’s assume that we’ve gone through the same steps as we did for my previous post:

Using Azure Change Tracking and Inventory to monitor Windows Services
http://terenceluk.blogspot.com/2022/02/using-azure-change-tracking-and.html

Monitoring and alerting has already been setup and what needs to be done is to incorporate automation as shown in the following.

PowerShell cmdlet Invoke-AzVMRunCommand

The PowerShell cmdlet Invoke-AzVMRunCommand is a cmdlet that allows running PowerShell scripts or commands remotely on an Azure Virtual Machine and this will be the method we’ll be using to remotely restart a stopped Windows service in a automation runbook. For more information about this cmdlet, see the following documentation:

Invoke-AzVMRunCommand
https://docs.microsoft.com/en-us/powershell/module/az.compute/invoke-azvmruncommand?view=azps-7.2.0

Run scripts in your Windows VM by using action Run Commands
https://docs.microsoft.com/en-us/azure/virtual-machines/windows/run-command

PowerShell script that uses the Invoke-AzVMRunCommand cmdlet to restart a virtual machine’s Windows service

Next, we’ll incorporate the cmdlet that allows us to restart a virtual machine’s Windows service into the following script:

# This PowerShell using Invoke-AzVMRunCommand runs the PS cmdlet with a parameter directly that specifies the Windows Service name on the target VM

Connect-AzAccount

$resourceGroupName = "yourResourceGroupName"

$vmName ="ServerName"

$scriptToRun = "Start-Service -DisplayName 'Remote Registry'"

Out-File -InputObject $scriptToRun -FilePath ScriptToRun.ps1

Invoke-AzVMRunCommand -ResourceGroupName $resourceGroupName -Name $vmName -CommandId 'RunPowerShellScript' -ScriptPath ScriptToRun.ps1

Remove-Item -Path ScriptToRun.ps1

This script will need the Virtual Machine Contributor role to execute the Invoke-AzVMRunCommand cmdlet and it will be provided with the Managed Identity for the Automation Account (configured a bit later) so the final script will have the following lines inserted to execute Connect-AzAccount with the Managed Identity:

# Ensures you do not inherit an AzContext in your runbook

Disable-AzContextAutosave -Scope Process

# Connect to Azure with system-assigned managed identity

$AzureContext = (Connect-AzAccount -Identity).context

# set and store context

$AzureContext = Set-AzContext -SubscriptionName $AzureContext.Subscription -DefaultProfile $AzureContext

# This PowerShell using Invoke-AzVMRunCommand runs the PS cmdlet with a parameter directly that specifies the Windows Service name on the target VM

$resourceGroupName = "yourResourceGroupName"

$vmName ="ServerName"

$scriptToRun = "Start-Service -DisplayName 'Remote Registry'"

Out-File -InputObject $scriptToRun -FilePath ScriptToRun.ps1

Invoke-AzVMRunCommand -ResourceGroupName $resourceGroupName -Name $vmName -CommandId 'RunPowerShellScript' -ScriptPath ScriptToRun.ps1

Remove-Item -Path ScriptToRun.ps1

Automation Account Runbook

With the PowerShell script for restarting a Windows service prepared, proceed to create a runbook that will execute the script when an alert. Navigate to the Automation Account > Runbooks and Create a runbook:

image

The options available for the Runbook type are as follows:

  1. PowerShell
  2. Python
  3. PowerShell Workflow
  4. Graphical PowerShell
  5. Graphical PowerShell Workflow
image

This example will use a PowerShell script so we’ll select PowerShell with the runtime version as 5.1 and then create the runbook:

image

With the runbook created, navigate into the newly created runbook and click on the Edit button:

image

Insert the prepared script into the Runbook:

image

Proceed to click Save and then Publish to publish the PowerShell Runbook. Note that we won’t be testing this just yet because we haven’t configured the managed identity yet and therefore the runbook does not have the appropriate permissions to use the Invoke-AzVMRunCommand to start a Windows service on the VM.

Managed Identity

With the runbook created, we’ll need to configure a managed identity for the Automation account to run the PowerShell script. More documentation about managed identity can be found in the following documentation:

Using a system-assigned managed identity for an Azure Automation account
https://docs.microsoft.com/en-us/azure/automation/enable-managed-identity-for-automation

Navigate to the Automation Account > Identity > System assigned and switch the Status to On to enable a system assigned managed identity:

image

Next, click on the Azure role assignments and add the Virtual Machine Contributor role to the assignment:

image

The Automation Account now has the assigned role for the PowerShell script to execute with.

Test a Automation Account Runbook

With the managed identity configured, we can now proceed to test the PowerShell script and verify that it indeed starts the Windows service. Navigate into the runbook and click on the Edit button:

image

Click on the Test pane button:

image

Click on the Start button to execute the PowerShell script using the managed identity:

image

The runbook will now execute the script:

Queued..

Streams will display when the test completes.

image

Wait for the test complete and verify that the output indicates it succeeded without errors:

image

Proceed to verify that the Windows service on the VM has restarted:

image

Create an Alert to monitor for service down and create Action Group with Automation Account Runbook

With the Automation Account Runbook tested, let’s proceed to create an alert to detect when the Remote Registry service (or any service of your choice) has stopped. Navigate to the Log Analytics workspace that was created to monitor the service, click on Create > Alert rule:

image

Select Custom Log Search to provide a custom Kusto query:

image

Many posts, including a previous one I wrote, simply use the following query to look for when a service has stopped:

ConfigurationData

| where SvcName =~ "RemoteRegistry"

| project SvcName, SvcDisplayName, SvcState, TimeGenerated

| where SvcState != "Running"

I find that the issue with using this query is that it will return all records of when the service has not been running within the specified period. This means that if the service was restarted and is running afterwards then this query will not show it and therefore the alert would continue to be fired. After giving it bit of thought, what I wanted to was run a query to get the last time the service was running and the last time it was stopped then compare the two TimeGenerated. Given my lack of experience with Kusto query, I cannot figure out how I can take the time and compare them so I decided to capture the two queries in variables, join them together, then compare the time stamps as shown below:

let LastStopped =

ConfigurationData

| where SvcName =~ "RemoteRegistry"

and SvcState != "Running"

| project SvcName, SvcDisplayName, SvcState, TimeGenerated

| order by TimeGenerated desc

| limit 1;

let LastRunning =

ConfigurationData

| where SvcName =~ "RemoteRegistry"

and SvcState == "Running"

| project SvcName, SvcDisplayName, SvcState, TimeGenerated

| order by TimeGenerated desc

| limit 1;

LastRunning

| join LastStopped on SvcName

// LastRunning time is earlier than LastStopped time

| where TimeGenerated <= TimeGenerated1

The intention of this query is to only return a result if the most recent event was when the service was not running and to not return anything if the most recent event was when the service is running. I’m completely open to recommendations if anyone happen to read this and think there is a better way of doing it.

image

With the query in place, proceed to create the Alert by clicking on Continue Editing Alert to use the query, leave the rest of the conditions as default and click on Actions:

image

Create a new action group by clicking on Create action group:

image

Provide a name for this action group and click on Notifications:

image

Configuration the notification setting and then click on Actions:

image

Select Automation Runbook for the Action type:

image

Select the runbook that was created earlier and click OK:

image

Proceed to create the Action Group:

image

image

The newly created Action group should automatically be added to the Alert:

image

Fill in the Details tab for the alert:

image

Complete creating the Alert:

image

Test Alert and Automation Account Runbook

Proceed to test and confirm that an alert is fired when the service is down and the runbook has successfully executed to restart the service:

image

image

I hope this blog post is able to provide information on how to set up an Automation Account to monitor a virtual machine’s Windows service and a runbook that will execute a PowerShell script using the Invoke-AzVMRunCommand to restart a stopped service.

Monday, April 13, 2020

PowerShell script to match Azure Virtual Machine name with Computer name in guest OS

I’ve recently taken on a project to review a client’s Azure environment and address all the risks associated with the lack of redundancy in its current design and the first task was to inventory their virtual machines so we can map them accordingly to the function it provides. One of my colleagues who have been working with this client forewarned me that there would be few name mismatches between the name given to the virtual machine in Azure and the name of the computer in the operating system. What may be named as SQL potentially could have been repurposed to a completely different function.

To clarify, the name I’m referring to is the configuration on the top left corner of the Azure virtual machine as compared to the value under the Computer name field, which is what, say, a computer is named within the Windows operating system:

image

There isn’t an easy way to retrieve this information so I did a quick search on the internet to see if there was a script available and was not able to find one so I wrote one using the Get-AzVM cmdlet to quickly retrieve, compare and output in the PowerShell console. More refinement can be made to the script but this quick and dirty job provided me with what I need so please feel to expand it as necessary.

Begin by copying the following and saving it as a .ps1 file:

foreach ( $azVM in Get-AzVM ) {

$networkProfile = $azVm.NetworkProfile.NetworkInterfaces.id.Split("/")|Select -Last 1

$vmName = $azVm.OsProfile.ComputerName

$rgName = $azVm.ResourceGroupName

#$tags = $azVM.Tags

$hostname = (Get-AzVM -ResourceGroupName $rgName -Name $azVm.OsProfile.ComputerName -status).computername

$IPConfig = (Get-AzNetworkInterface -Name $networkProfile).IpConfigurations.PrivateIpAddress

[pscustomobject]@{

Name = $azVm.OsProfile.ComputerName

ComputerName = $hostname

"IP Addresses" = $IPConfig

#Tags = $tags

"Az Name Match Host Name" = $azVm.OsProfile.ComputerName.equals($hostname)

}

}

Here is a screenshot of what it looks like in the Windows PowerShell ISE editor:

image

With the script saved in a directory, proceed to connect to Azure:

Connect-AzAccount

image

The environment in this example redirects the login to a Citrix ADC that performs AAA authentication with DUO MFA:

imageimage

Your subscription name, tenant ID will be displayed upon successful authentication:

image

Navigate to the directory with the script and execute it with .\<scriptname>.ps1:

image

As mentioned previously, more refinement such as handling blank Computer Names differently or exporting it to a CSV instead. Also note that I originally included outputting the Tags but have commented it out in the script.