Testing Azure Machine Configuration Using Policy
In my home lab, I have some automation of the configuration of virtual machines. For that configuration I use Ansible. But all of the virtual machines are Linux. This is the first attempt I have on using machine configuration with guest configuration agent and policy, in Azure to configure a VM. I have tried to configure Windows virtual machines earlier using Ansible. Ansible uses some DSC modules for some configurations, and I also use DSC in this configuration.
Prerequisites
Client machine:
I use a Windows 11 machine with this setup:
Powershell v7 - winget install --id Microsoft.Powershell --source winget
Guest Configuration - install-Module -Name GuestConfiguration
Import module - import-Module GuestConfiguration
Test if the module is available - get-module GuestConfiguration
For this test, I did use those DSC modules PSDesiredStateConfiguration, AuditPolicyDsc, and SecurityPolicyDSC - Install-Module -Name PSDesiredStateConfiguration, AuditPolicyDsc, SecurityPolicyDSC
Azure Virtual Machine:
The virtual machine in Azure needs this (both requirements can be deployed using policy):
- The Guest Configuration extension (not required for a virtual machine that uses Arc)
- A system-assigned identity
In the subscription, you need to enable the Microsoft.GuestConfiguration
provider.
You also need a storage account with a container to save the artefact to. Make sure that the client you are using and the Azure VM have access to the container since you need to upload the artefact we are creating later and the virtual machine will download the config from that container.
I used Terraform to configure all of the resources in Azure with the exception of the definition policy. I will not share the steps that I used in Terraform, since this post is long enough.
Create the DSC config
I created a config that does two things. The first, disable the Guest account. The second one changes the Enforce password history
to 15.
Configuration SecurityBaseline
{
Import-DscResource -ModuleName AuditPolicyDsc
Import-DscResource -ModuleName SecurityPolicyDSC
node localhost
{
SecurityOption SecurityOptions
{
Name = 'SecurityOption'
Accounts_Guest_account_status = 'Disabled'
}
AccountPolicy AccountPolicies
{
Name = 'PasswordPolicies'
Enforce_password_history = 15
}
}
}
SecurityBaseline -OutputPath .\SecurityBaselineTest\MOF\
Save it to a .ps1
file and run it. You should now have a .mof
file in the directory that you specified in the last line of the script. The .mof
file that was created is called localhost.mof
. Use this command to rename the file Rename-Item -Path .\SecurityBaselineTest\MOF\localhost.mof -NewName SecurityBaseline.mof -PassThru
.
Create the artifact
The next step is to create the .zip
file that contains the configuration, modules and all the files that are needed for Azure Machine Configuration to use this file. To do that, you could create a PowerShell script that contains this config:
$params = @{
Name = 'SecurityBaselineTest'
Configuration = './SecurityBaselineTest/MOF/SecurityBaseline.mof'
Type = 'AuditAndSet'
Force = $true
}
New-GuestConfigurationPackage @params
The important part here is to choose the correct Type
. There are two different variables, AuditAndSet
and Audit
. Audit
is the default. When you run this script, it will create a .zip
file.
Create, upload and assign the policy
In this step, we are creating the last script we need, but the first step is to create a folder in the storage account container you created earlier. In my case, I called it machine-configuration
.
$PolicyConfig = @{
PolicyId = (New-Guid)
ContentUri = 'https://somethingstrange.blob.core.windows.net/machine-configuration/SecurityBaselineTest.zip'
DisplayName = 'SecurityBaseline1'
Description = 'SecurityBaseline1'
Path = './SecurityBaselineTest/policies/SecurityBaselineTest.json'
Platform = 'Windows'
PolicyVersion = '1.0.0'
Mode = 'ApplyAndAutoCorrect'
}
New-GuestConfigurationPolicy @PolicyConfig
Run the script and you get a .json
file. In Azure, create a definition and give it a logical name. Like SecurityBaseline1
. If you don’t want to do this manually in the portal you can use this command to create the definition - New-AzPolicyDefinition -Name 'SecurityBaseline1' -Policy /SecurityBaselineTest/policies/SecurityBaselineTest.json
. Now you have created a policy definition, that you can assign to something. In my case, I did assign it to the resource group where my virtual machine is located. During the process where you assign the policy, there are a couple of important tasks. The first on, set the parameter IncludeArcMachines
to true
if you want the police to work on virtual machines that are using Azure Arc. The second important part is that during the creation of the policy assignment, you need to create an identity and give it a role. I did use a System Managed Identity
and gave it the Guest Configuration Resource Contributor
.
Test
Go to your virtual machine in Azure, and check out the policy tab. There you will see that the new policy is listed, but with the status Non-compliant
. Open the policy and create a Remediation task
. This will run the policy (the policy is DeployIfNotExist) and after some time you will see that the policy status changes to Compliant
. Next, go into the Machine Configuration tab in your virtual machine in Azure. Here you will see that the machine configuration is there and it says that 100% (2 out of 2)
rules are compliant. The type should be ApplyAndAutoCorrect
.
If you log into the virtual machine, and check the Local Security Policy
, the Enforce Password History
is changed to the value I used in the script earlier (15).
Links
matrixpost - Guest Configuration Extension on the Client
matrixpost - Guest Configuration Extension on the Client
Microsoft Learn - Azure Machine Configuration documentation
Pratheep Sinnathurai - Security Baseline on Azure Arc-Enabled Servers using Machine Configuration