Onboard machines to Azure Machine Configuration (the modern, cross platform successor to Azure
Automation State Configuration, which retires on 2027-09-30) and audit or enforce PowerShell DSC in
guest state. Assign the built in compute security and CIS baselines across a scope, and apply custom
DSC packages to individual Windows and Linux VMs with continuous drift correction
(ApplyAndAutoCorrect). Windows uses PSDSC v2, Linux uses PSDSC v3, and the same model covers Azure
and Arc enabled servers.
- Onboarding. Installs the Guest Configuration extension (
AzurePolicyforWindows/AzurePolicyforLinux) on the VMs you name, so per machine assignments actually execute. The VM must carry a system assigned identity (the Libre DevOps VM modules set one by default). - Built in baselines at scale. A curated
builtincatalog (verified live) resolves friendly keys (windows_cis,linux_cis,windows_compute_baseline,linux_compute_baseline,prereq_system_assigned) to the right built in definition or initiative, assigned across a resource group, subscription, or management group. Audit by default; enforce is opt in. - Custom DSC packages per machine. Point a machine at a custom package (
content_uriplus the UPPERCASE SHA256content_hash) and chooseAudit,ApplyAndMonitor, orApplyAndAutoCorrect. Build the packages with theLibreDevOpsHelpersMachine Configuration helpers (which wrap theGuestConfigurationPowerShell module), as thecompleteexample and CI do. - Remediation wiring. Assignments that deploy (
DeployIfNotExists) get a system assigned identity and the role grants they need.
module "machine_configuration" {
source = "libre-devops/machine-configuration/azurerm"
# Onboard a VM (install the Guest Configuration extension).
guest_configuration_extensions = {
AzurePolicyforLinux = {
virtual_machine_id = module.linux_vm.virtual_machine_ids["vm-lnx-ldo-uks-dev-01"]
os_type = "Linux"
}
}
# Audit the Linux CIS baseline across the resource group.
policy_assignments = {
"mc-linux-cis" = {
display_name = "Linux CIS baseline (audit)"
scope_type = "resource_group"
scope_id = module.rg.ids["rg-ldo-uks-dev-01"]
location = "uksouth"
builtin = "linux_cis"
}
}
}examples/minimal- the smallest valid call (required inputs only).examples/complete- every supported input exercised.
Local work needs PowerShell 7+ and just, because the recipes
wrap the LibreDevOpsHelpers
PowerShell module (the same engine the libre-devops/terraform-azure action runs in CI). Install
just with brew install just, or uv tool add rust-just then uv run just <recipe>.
Run just to list recipes: just update-ldo-pwsh (install or force-update LibreDevOpsHelpers from
PSGallery), just validate, just scan (Trivy only), just pwsh-analyze (PSScriptAnalyzer only),
just plan, just apply, just destroy, just e2e, just test, and just docs (the
plan/apply/destroy recipes mirror the action, including the storage firewall dance; just e2e
applies an example then always destroys it, defaulting to minimal, so nothing is left running).
Releasing is also just:
just increment-release [patch|minor|major] bumps, tags, and publishes a GitHub release, and the
Terraform Registry picks up the tag.
This module is scanned with Trivy; HIGH and CRITICAL
findings fail the build. Any waiver is a deliberate, reviewed decision, never a way to quiet a
finding that should be fixed. Waivers live in .trivyignore.yaml (the
machine-applied source of truth, passed to Trivy with --ignorefile) and are mirrored in the table
below so the reason is auditable.
| Trivy ID | Resource | Finding | Justification |
|---|---|---|---|
| AVD-AZU-0013 | examples/complete key vault |
Vault network ACL does not block by default | Disposable example vault, created + written + read + destroyed in one apply, so it cannot be IP allow-listed before it exists. Public network Allow lets the CI runner write the demo secrets without a per-run firewall dance (the Libre DevOps example convention, matching the linux-vm module). A real vault stays firewalled (module default) and uses the action's allow-list dance. The module creates no key vault. |
To add an exception: add an entry to .trivyignore.yaml (id, optional paths to scope it, and a
statement recording why), then add a matching row here. Where the finding is out of this module's
scope, point the justification at the Libre DevOps module that does address it (for example the
private-endpoint module). Both the file and this table are reviewed in the pull request.
The Requirements, Providers, Inputs, Outputs, and Resources below are generated by terraform-docs.
| Name | Version |
|---|---|
| terraform | >= 1.9.0, < 2.0.0 |
| azurerm | >= 4.0.0, < 5.0.0 |
| Name | Version |
|---|---|
| azurerm | >= 4.0.0, < 5.0.0 |
No modules.
| Name | Type |
|---|---|
| azurerm_management_group_policy_assignment.this | resource |
| azurerm_policy_virtual_machine_configuration_assignment.this | resource |
| azurerm_resource_group_policy_assignment.this | resource |
| azurerm_role_assignment.remediation | resource |
| azurerm_subscription_policy_assignment.this | resource |
| azurerm_virtual_machine_extension.guest_config | resource |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| guest_configuration_extensions | Guest Configuration VM extensions to install, keyed by a stable name. Installing the extension is the onboarding step: a per machine configuration assignment will not execute until the extension is present on the virtual machine, and the virtual machine must carry a system assigned identity so the platform can report and remediate. Set os_type to Windows or Linux; the module picks the correct publisher and type per OS when they are not overridden. |
map(object({ |
{} |
no |
| machine_configuration_assignments | Per machine (guest configuration) assignments, keyed by a stable name. Onboard a specific virtual machine to a built in baseline (for example AzureWindowsBaseline or AzureLinuxBaseline, leaving content_uri and content_hash unset) or to a custom DSC package (both content_uri and content_hash required; content_hash is the UPPERCASE SHA256 of the .zip; the service requires a version for custom packages, so the module defaults it to 1.0.0 when unset). assignment_type defaults to Audit (the safe default); ApplyAndAutoCorrect enforces continuously. The referenced virtual machine must already carry the Guest Configuration extension (see guest_configuration_extensions) and a system assigned identity. |
map(object({ |
{} |
no |
| policy_assignments | Scoped Guest Configuration policy assignments, keyed by a stable name: the fleet wide way to audit or enforce baselines across every machine in a resource group, subscription or management group. Point each entry at the curated built in catalog with builtin (for example linux_cis, windows_cis, linux_compute_baseline, windows_compute_baseline, prereq_system_assigned), or set policy_definition_id / policy_set_definition_id explicitly. Catalog definitions with required parameters are defaulted by the module (linux_cis: BaselineSettings, an empty string, the stock CIS baseline); anything in parameters overrides the default per key. enforcement_mode defaults to Default; set it to DoNotEnforce for report only. A system assigned identity plus remediation role grants are wired for definitions that deploy (DeployIfNotExists). |
map(object({ |
{} |
no |
| Name | Description |
|---|---|
| builtin_catalog | Curated map of friendly key to built in Guest Configuration definition/initiative id. Reference a key via policy_assignments[*].builtin, or read the id directly. |
| builtin_catalog_keys | The valid keys of builtin_catalog (the accepted values for policy_assignments[*].builtin). |
| guest_configuration_extension_ids_zipmap | Map of extension key to { name, id } for easy composition. |
| guest_configuration_extensions | Map of extension key to the installed Guest Configuration extension (id, name, type, virtual_machine_id). |
| machine_configuration_assignment_ids_zipmap | Map of assignment key to { name, id } for easy composition. |
| machine_configuration_assignments | Map of assignment key to the per machine Guest Configuration assignment (id, name, virtual_machine_id). |
| policy_assignment_ids_zipmap | Map of assignment key to { name, id } across every scope, for easy composition. |
| policy_assignment_principal_ids | Map of assignment key to its system assigned identity principal id (null when identity is None). |
| policy_assignments | Map of assignment key to the scoped policy assignment (id, name, scope_type, principal_id of its identity). |
| remediation_role_assignment_ids | Map of ':' to the created role assignment id. |