How to create Guest Configuration policies for Windows

Before creating custom policy definitions, it's a good idea to read the conceptual overview information at the page Azure Policy Guest Configuration.

To learn about creating Guest Configuration policies for Linux, see the page How to create Guest Configuration policies for Linux

When auditing Windows, Guest Configuration uses a Desired State Configuration (DSC) resource module to create the configuration file. The DSC configuration defines the condition that the machine should be in. If the evaluation of the configuration fails, the policy effect auditIfNotExists is triggered and the machine is considered non-compliant.

Azure Policy Guest Configuration can only be used to audit settings inside machines. Remediation of settings inside machines isn't yet available.

Use the following actions to create your own configuration for validating the state of an Azure or non-Azure machine.

Important

Custom policy definitions with Guest Configuration in the Azure Government and Azure China environments is a Preview feature.

The Guest Configuration extension is required to perform audits in Azure virtual machines. To deploy the extension at scale across all Windows machines, assign the following policy definitions: Deploy prerequisites to enable Guest Configuration Policy on Windows VMs

Don't use secrets or confidential information in custom content packages.

Install the PowerShell module

The Guest Configuration module automates the process of creating custom content including:

  • Creating a Guest Configuration content artifact (.zip)
  • Automated testing of the artifact
  • Creating a policy definition
  • Publishing the policy

The module can be installed on a machine running Windows, macOS, or Linux with PowerShell 6.2 or later running locally, or with Azure Cloud Shell, or with the Azure PowerShell Core Docker image.

Note

Compilation of configurations is not yet supported on Linux.

Base requirements

Operating Systems where the module can be installed:

  • Linux
  • macOS
  • Windows

The Guest Configuration resource module requires the following software:

  • PowerShell 6.2 or later. If it isn't yet installed, follow these instructions.
  • Azure PowerShell 1.5.0 or higher. If it isn't yet installed, follow these instructions.
    • Only the Az modules 'Az.Accounts' and 'Az.Resources' are required.

Install the module

To install the GuestConfiguration module in PowerShell:

  1. From a PowerShell prompt, run the following command:

    # Install the Guest Configuration DSC resource module from PowerShell Gallery
    Install-Module -Name GuestConfiguration
    
  2. Validate that the module has been imported:

    # Get a list of commands for the imported GuestConfiguration module
    Get-Command -Module 'GuestConfiguration'
    

Guest Configuration artifacts and policy for Windows

Guest Configuration uses PowerShell Desired State Configuration as a language abstraction for writing what to audit in Windows. The agent loads a standalone instance of PowerShell 6.2, so there isn't conflict with usage of PowerShell DSC in Windows PowerShell 5.1, and there's no requirement to pre-install PowerShell 6.2 or later.

For an overview of DSC concepts and terminology, see PowerShell DSC Overview.

How Guest Configuration modules differ from Windows PowerShell DSC modules

When Guest Configuration audits a machine the sequence of events is different than in Windows PowerShell DSC.

  1. The agent first runs Test-TargetResource to determine if the configuration is in the correct state.
  2. The boolean value returned by the function determines if the Azure Resource Manager status for the Guest Assignment should be Compliant/Not-Compliant.
  3. The provider runs Get-TargetResource to return the current state of each setting so details are available both about why a machine isn't compliant and to confirm that the current state is compliant.

Parameters in Azure Policy that pass values to Guest Configuration assignments must be string type. It isn't possible to pass arrays through parameters, even if the DSC resource supports arrays.

Get-TargetResource requirements

The function Get-TargetResource has special requirements for Guest Configuration that haven't been needed for Windows Desired State Configuration.

  • The hashtable that is returned must include a property named Reasons.
  • The Reasons property must be an array.
  • Each item in the array should be a hashtable with keys named Code and Phrase.

The Reasons property is used by the service to standardize how information is presented when a machine is out of compliance. You can think of each item in Reasons as a "reason" that the resource isn't compliant. The property is an array because a resource could be out of compliance for more than one reason.

The properties Code and Phrase are expected by the service. When authoring a custom resource, set the text (typically stdout) you would like to show as the reason the resource isn't compliant as the value for Phrase. Code has specific formatting requirements so reporting can clearly display information about the resource used to do the audit. This solution makes Guest Configuration extensible. Any command could be run as long as the output can be returned as a string value for the Phrase property.

  • Code (string): The name of the resource, repeated, and then a short name with no spaces as an identifier for the reason. These three values should be colon-delimited with no spaces.
    • An example would be registry:registry:keynotpresent
  • Phrase (string): Human-readable text to explain why the setting isn't compliant.
    • An example would be The registry key $key is not present on the machine.
$reasons = @()
$reasons += @{
  Code = 'Name:Name:ReasonIdentifer'
  Phrase = 'Explain why the setting is not compliant'
}
return @{
    reasons = $reasons
}

The Reasons property must be added to the schema MOF for the resource as an embedded class.

[ClassVersion("1.0.0.0")] 
class Reason
{
    [Read] String Phrase;
    [Read] String Code;
};

[ClassVersion("1.0.0.0"), FriendlyName("ResourceName")]
class ResourceName : OMI_BaseResource
{
    [Key, Description("Example description")] String Example;
    [Read, EmbeddedInstance("Reason")] String Reasons[];
};

If the resource has required properties, those properties must also be returned by Get-TargetResource in parallel with the reasons class. If reasons isn't included, the service includes a "catch-all" behavior that compares the values input to Get-TargetResource and the values returned by Get-TargetResource, and provides a detailed comparison as reasons.

Configuration requirements

The name of the custom configuration must be consistent everywhere. The name of the .zip file for the content package, the configuration name in the MOF file, and the guest assignment name in the Azure Resource Manager template (ARM template), must be the same.

Policy requirements

The policy definition metadata section must include two properties for the Guest Configuration service to automate provisioning and reporting of Guest Configuration assignments. The category property must be set to "Guest Configuration" and a section named Guest Configuration must contain information about the Guest Configuration assignment. The New-GuestConfigurationPolicy cmdlet creates this text automatically. See the step-by-step instructions on this page.

The following example demonstrates the metadata section.

    "metadata": {
      "category": "Guest Configuration",
      "guestConfiguration": {
        "name": "test",
        "version": "1.0.0",
        "contentType": "Custom",
        "contentUri": "CUSTOM-URI-HERE",
        "contentHash": "CUSTOM-HASH-VALUE-HERE",
        "configurationParameter": {}
      }
    },

Scaffolding a Guest Configuration project

Developers who would like to accelerate the process of getting started and work from sample code can install a community project named Guest Configuration Project. The project installs a template for the Plaster PowerShell module. This tool can be used to scaffold a project including a working configuration and sample resource, and a set of Pester tests to validate the project. The template also includes task runners for Visual Studio Code to automate building and validating the Guest Configuration package. For more information, see the GitHub project Guest Configuration Project.

For more information about working with configurations in general, see Write, Compile, and Apply a Configuration.

Expected contents of a Guest Configuration artifact

The completed package is used by Guest Configuration to create the Azure Policy definitions. The package consists of:

  • The compiled DSC configuration as a MOF
  • Modules folder
    • GuestConfiguration module
    • DscNativeResources module
    • (Windows) DSC resource modules required by the MOF

PowerShell cmdlets assist in creating the package. No root level folder or version folder is required. The package format must be a .zip file and can't exceed a total size of 100 MB when uncompressed.

Storing Guest Configuration artifacts

The .zip package must be stored in a location that is accessible by the managed virtual machines. Examples include GitHub repositories, an Azure Repo, or Azure storage. If you prefer to not make the package public, you can include a SAS token in the URL. You could also implement service endpoint for machines in a private network, although this configuration applies only to accessing the package and not communicating with the service.

Step by step, creating a custom Guest Configuration audit policy for Windows

Create a DSC configuration to audit settings. The following PowerShell script example creates a configuration named AuditBitLocker, imports the PsDscResources resource module, and uses the Service resource to audit for a running service. The configuration script can be executed from a Windows or macOS machine.

# Add PSDscResources module to environment
Install-Module 'PSDscResources'

# Define the DSC configuration and import GuestConfiguration
Configuration AuditBitLocker
{
    Import-DscResource -ModuleName 'PSDscResources'

    Node AuditBitlocker {
      Service 'Ensure BitLocker service is present and running'
      {
          Name = 'BDESVC'
          Ensure = 'Present'
          State = 'Running'
      }
    }
}

# Compile the configuration to create the MOF files
AuditBitLocker

Run this script in a PowerShell terminal or save this file with name config.ps1 in the project folder. Run it in PowerShell by executing ./config.ps1 in the terminal. A new mof file is created.

The Node AuditBitlocker command isn't technically required but it produces a file named AuditBitlocker.mof rather than the default, localhost.mof. Having the .mof file name follow the configuration makes it easy to organize many files when operating at scale.

Once the MOF is compiled, the supporting files must be packaged together. The completed package is used by Guest Configuration to create the Azure Policy definitions.

The New-GuestConfigurationPackage cmdlet creates the package. Modules that are needed by the configuration must be in available in $Env:PSModulePath. Parameters of the New-GuestConfigurationPackage cmdlet when creating Windows content:

  • Name: Guest Configuration package name.
  • Configuration: Compiled DSC configuration document full path.
  • Path: Output folder path. This parameter is optional. If not specified, the package is created in current directory.

Run the following command to create a package using the configuration given in the previous step:

New-GuestConfigurationPackage `
  -Name 'AuditBitlocker' `
  -Configuration './AuditBitlocker/AuditBitlocker.mof'

After creating the Configuration package but before publishing it to Azure, you can test the package from your workstation or continuous integration and continuous deployment (CI/CD) environment. The GuestConfiguration cmdlet Test-GuestConfigurationPackage includes the same agent in your development environment as is used inside Azure machines. Using this solution, you can do integration testing locally before releasing to billed cloud environments.

Since the agent is actually evaluating the local environment, in most cases you need to run the Test- cmdlet on the same OS platform as you plan to audit. The test only uses modules that are included in the content package.

Parameters of the Test-GuestConfigurationPackage cmdlet:

  • Name: Guest Configuration policy name.
  • Parameter: Policy parameters provided in hashtable format.
  • Path: Full path of the Guest Configuration package.

Run the following command to test the package created by the previous step:

Test-GuestConfigurationPackage `
  -Path ./AuditBitlocker.zip

The cmdlet also supports input from the PowerShell pipeline. Pipe the output of New-GuestConfigurationPackage cmdlet to the Test-GuestConfigurationPackage cmdlet.

New-GuestConfigurationPackage -Name AuditBitlocker -Configuration ./AuditBitlocker/AuditBitlocker.mof | Test-GuestConfigurationPackage

The next step is to publish the file to Azure Blob Storage. There are no special requirements for the storage account, but it's a good idea to host the file in a region near your machines. If you don't have a storage account, use the following example. The commands below, including Publish-GuestConfigurationPackage, require the Az.Storage module.

# Creates a new resource group, storage account, and container
New-AzResourceGroup -name myResourceGroupName -Location WestUS
New-AzStorageAccount -ResourceGroupName myResourceGroupName -Name myStorageAccountName -SkuName 'Standard_LRS' -Location 'WestUs' | New-AzStorageContainer -Name guestconfiguration -Permission Blob

Parameters of the Publish-GuestConfigurationPackage cmdlet:

  • Path: Location of the package to be published
  • ResourceGroupName: Name of the resource group where the storage account is located
  • StorageAccountName: Name of the storage account where the package should be published
  • StorageContainerName: (default: guestconfiguration) Name of the storage container in the storage account
  • Force: Overwrite existing package in the storage account with the same name

The example below publishes the package to a storage container name 'guestconfiguration'.

Publish-GuestConfigurationPackage -Path ./AuditBitlocker.zip -ResourceGroupName myResourceGroupName -StorageAccountName myStorageAccountName

Once a Guest Configuration custom policy package has been created and uploaded, create the Guest Configuration policy definition. The New-GuestConfigurationPolicy cmdlet takes a custom policy package and creates a policy definition.

Parameters of the New-GuestConfigurationPolicy cmdlet:

  • ContentUri: Public http(s) uri of Guest Configuration content package.
  • DisplayName: Policy display name.
  • Description: Policy description.
  • Parameter: Policy parameters provided in hashtable format.
  • Version: Policy version.
  • Path: Destination path where policy definitions are created.
  • Platform: Target platform (Windows/Linux) for Guest Configuration policy and content package.
  • Tag adds one or more tag filters to the policy definition
  • Category sets the category metadata field in the policy definition

The following example creates the policy definitions in a specified path from a custom policy package:

New-GuestConfigurationPolicy `
    -ContentUri 'https://storageaccountname.blob.core.windows.net/packages/AuditBitLocker.zip?st=2019-07-01T00%3A00%3A00Z&se=2024-07-01T00%3A00%3A00Z&sp=rl&sv=2018-03-28&sr=b&sig=JdUf4nOCo8fvuflOoX%2FnGo4sXqVfP5BYXHzTl3%2BovJo%3D' `
    -DisplayName 'Audit BitLocker Service.' `
    -Description 'Audit if BitLocker is not enabled on Windows machine.' `
    -Path './policies' `
    -Platform 'Windows' `
    -Version 1.0.0 `
    -Verbose

The following files are created by New-GuestConfigurationPolicy:

  • auditIfNotExists.json

The cmdlet output returns an object containing the initiative display name and path of the policy files.

Finally, publish the policy definitions using the Publish-GuestConfigurationPolicy cmdlet. The cmdlet only has the Path parameter that points to the location of the JSON files created by New-GuestConfigurationPolicy.

To run the Publish command, you need access to create policies in Azure. The specific authorization requirements are documented in the Azure Policy Overview page. The best built-in role is Resource Policy Contributor.

Publish-GuestConfigurationPolicy -Path '.\policyDefinitions'

The Publish-GuestConfigurationPolicy cmdlet accepts the path from the PowerShell pipeline. This feature means you can create the policy files and publish them in a single set of piped commands.

New-GuestConfigurationPolicy `
 -ContentUri 'https://storageaccountname.blob.core.windows.net/packages/AuditBitLocker.zip?st=2019-07-01T00%3A00%3A00Z&se=2024-07-01T00%3A00%3A00Z&sp=rl&sv=2018-03-28&sr=b&sig=JdUf4nOCo8fvuflOoX%2FnGo4sXqVfP5BYXHzTl3%2BovJo%3D' `
  -DisplayName 'Audit BitLocker service.' `
  -Description 'Audit if the BitLocker service is not enabled on Windows machine.' `
  -Path './policies' `
 | Publish-GuestConfigurationPolicy

With the policy created in Azure, the last step is to assign the definition. See how to assign the definition with Portal, Azure CLI, and Azure PowerShell.

Filtering Guest Configuration policies using Tags

The policy definitions created by cmdlets in the Guest Configuration module can optionally include a filter for tags. The Tag parameter of New-GuestConfigurationPolicy supports an array of hashtables containing individual tag entires. The tags are added to the If section of the policy definition and can't be modified by a policy assignment.

An example snippet of a policy definition that filters for tags is given below.

"if": {
  "allOf" : [
    {
      "allOf": [
        {
          "field": "tags.Owner",
          "equals": "BusinessUnit"
        },
        {
          "field": "tags.Role",
          "equals": "Web"
        }
      ]
    },
    {
      // Original Guest Configuration content
    }
  ]
}

Using parameters in custom Guest Configuration policy definitions

Guest Configuration supports overriding properties of a Configuration at run time. This feature means that the values in the MOF file in the package don't have to be considered static. The override values are provided through Azure Policy and don't change how the Configurations are authored or compiled.

The cmdlets New-GuestConfigurationPolicy and Test-GuestConfigurationPolicyPackage include a parameter named Parameter. This parameter takes a hashtable definition including all details about each parameter and creates the required sections of each file used for the Azure Policy definition.

The following example creates a policy definition to audit a service, where the user selects from a list at the time of policy assignment.

# This DSC Resource text:
Service 'UserSelectedNameExample'
      {
          Name = 'ParameterValue'
          Ensure = 'Present'
          State = 'Running'
      }

# Would require the following hashtable:
$PolicyParameterInfo = @(
    @{
        Name = 'ServiceName'                                            # Policy parameter name (mandatory)
        DisplayName = 'windows service name.'                           # Policy parameter display name (mandatory)
        Description = "Name of the windows service to be audited."      # Policy parameter description (optional)
        ResourceType = "Service"                                        # DSC configuration resource type (mandatory)
        ResourceId = 'UserSelectedNameExample'                          # DSC configuration resource id (mandatory)
        ResourcePropertyName = "Name"                                   # DSC configuration resource property name (mandatory)
        DefaultValue = 'winrm'                                          # Policy parameter default value (optional)
        AllowedValues = @('BDESVC','TermService','wuauserv','winrm')    # Policy parameter allowed values (optional)
    }
)

New-GuestConfigurationPolicy
    -ContentUri 'https://storageaccountname.blob.core.windows.net/packages/AuditBitLocker.zip?st=2019-07-01T00%3A00%3A00Z&se=2024-07-01T00%3A00%3A00Z&sp=rl&sv=2018-03-28&sr=b&sig=JdUf4nOCo8fvuflOoX%2FnGo4sXqVfP5BYXHzTl3%2BovJo%3D' `
    -DisplayName 'Audit Windows Service.' `
    -Description 'Audit if a Windows Service is not enabled on Windows machine.' `
    -Path '.\policyDefinitions' `
    -Parameter $PolicyParameterInfo `
    -Version 1.0.0

Extending Guest Configuration with third-party tools

The artifact packages for Guest Configuration can be extended to include third-party tools. Extending Guest Configuration requires development of two components.

  • A Desired State Configuration resource that handles all activity related to managing the third-party tool
    • Install
    • Invoke
    • Convert output
  • Content in the correct format for the tool to natively consume

The DSC resource requires custom development if a community solution doesn't already exist. Community solutions can be discovered by searching the PowerShell Gallery for tag GuestConfiguration.

Note

Guest Configuration extensibility is a "bring your own license" scenario. Ensure you have met the terms and conditions of any third party tools before use.

After the DSC resource has been installed in the development environment, use the FilesToInclude parameter for New-GuestConfigurationPackage to include content for the third-party platform in the content artifact.

Policy lifecycle

If you would like to release an update to the policy, make the change for both the Guest Configuration package and the Azure Policy definition details.

Note

The version property of the Guest Configuration assignment only effects packages that are hosted by Microsoft. The best practice for versioning custom content is to include the version in the file name.

First, when running New-GuestConfigurationPackage, specify a name for the package that makes it unique from previous versions. You can include a version number in the name such as PackageName_1.0.0. The number in this example is only used to make the package unique, not to specify that the package should be considered newer or older than other packages.

Second, update the parameters used with the New-GuestConfigurationPolicy cmdlet following each of the explanations below.

  • Version: When you run the New-GuestConfigurationPolicy cmdlet, you must specify a version number greater than what is currently published.
  • contentUri: When you run the New-GuestConfigurationPolicy cmdlet, you must specify a URI to the location of the package. Including a package version in the file name will ensure the value of this property changes in each release.
  • contentHash: This property is updated automatically by the New-GuestConfigurationPolicy cmdlet. It's a hash value of the package created by New-GuestConfigurationPackage. The property must be correct for the .zip file you publish. If only the contentUri property is updated, the Extension won't accept the content package.

The easiest way to release an updated package is to repeat the process described in this article and provide an updated version number. That process guarantees all properties have been correctly updated.

Optional: Signing Guest Configuration packages

Guest Configuration custom policies use SHA256 hash to validate the policy package hasn't changed. Optionally, customers may also use a certificate to sign packages and force the Guest Configuration extension to only allow signed content.

To enable this scenario, there are two steps you need to complete. Run the cmdlet to sign the content package, and append a tag to the machines that should require code to be signed.

To use the Signature Validation feature, run the Protect-GuestConfigurationPackage cmdlet to sign the package before it's published. This cmdlet requires a 'Code Signing' certificate.

$Cert = Get-ChildItem -Path cert:\LocalMachine\My | Where-Object {($_.Subject-eq "CN=mycert") }
Protect-GuestConfigurationPackage -Path .\package\AuditWindowsService\AuditWindowsService.zip -Certificate $Cert -Verbose

Parameters of the Protect-GuestConfigurationPackage cmdlet:

  • Path: Full path of the Guest Configuration package.
  • Certificate: Code signing certificate to sign the package. This parameter is only supported when signing content for Windows.

GuestConfiguration agent expects the certificate public key to be present in "Trusted Root Certificate Authorities" on Windows machines and in the path /usr/local/share/ca-certificates/extra on Linux machines. For the node to verify signed content, install the certificate public key on the machine before applying the custom policy. This process can be done using any technique inside the VM or by using Azure Policy. An example template is provided here. The Key Vault access policy must allow the Compute resource provider to access certificates during deployments. For detailed steps, see Set up Key Vault for virtual machines in Azure Resource Manager.

Following is an example to export the public key from a signing certificate, to import to the machine.

$Cert = Get-ChildItem -Path cert:\LocalMachine\My | Where-Object {($_.Subject-eq "CN=mycert3") } | Select-Object -First 1
$Cert | Export-Certificate -FilePath "$env:temp\DscPublicKey.cer" -Force

After your content is published, append a tag with name GuestConfigPolicyCertificateValidation and value enabled to all virtual machines where code signing should be required. See the Tag samples for how tags can be delivered at scale using Azure Policy. Once this tag is in place, the policy definition generated using the New-GuestConfigurationPolicy cmdlet enables the requirement through the Guest Configuration extension.

Next steps