AzureVMCredentialRotation
One deployment, then it runs itself
Subscription scope, dry-run by default. The same deployment configures the runbook through eighteen CR_ automation variables, which is also what the Terraform path sets — a test compares the two sets on every push.
az deployment sub create \
--location switzerlandnorth \
--template-file deploy/main.bicep \
--parameters keyVaultName=kv-credentials \
keyVaultResourceGroupName=rg-vault \
targetResourceGroupNames='["rg-workloads"]'
# Then opt machines in, one tag at a time.
az vm update --ids $VM --set tags.CredentialRotation=enabledOne deployment creates
- An Automation Account with a system-assigned identity and the runbook published
- A schedule, and the role assignments it needs: Secrets Officer on the vault, Virtual Machine Contributor on the scopes you name
- Optionally, and on by default: a Log Analytics workspace, a custom table, and the workbook below: who read which credential, how long it stayed valid, and what is stuck
- Nothing on your VMs. Scope is opt-in by tag, so the first run over an untagged estate does nothing at all
Feature flags rather than stacked modules: deployObservability for the audit trail (on), enableRotateOnAccess for rotation after use (off), dryRun for whether anything is actually replaced (on, deliberately). Name resource groups or whole subscriptions anywhere in the tenant and the roles are assigned where the machines are.
Under the hood
One logic, two ways to run it
The module alone covers a handful of machines: install it, name a VM, done. Nothing deployed, nothing tagged. The orchestrator is for the fleet: one Automation Account that runs the same module every six hours, and every run does the same four things.

Find the machines in scope
Every VM that carries
CredentialRotation=enabled, in the resource groups or subscriptions you named. Opt-in on purpose: a loop that treated "no secret exists for this VM" as "rotate it" would change the local administrator password of every machine it can see on its first run.Who read a secret since the last run?
The vault's audit log says. For each secret a human read, the expiry date moves to eight hours from now. Nothing else happens yet; the next step picks it up. That is all of rotation-after-use, no queue and no second code path.
What is near expiry? Stage, apply, promote
Every secret is named
{vm}-{user}-{kind}, for examplejump-01-labadmin-pw. Within 14 days of expiry, or missing: the new value is written tojump-01-labadmin-pw-pendingfirst, set on the VM through the VMAccess extension second, and promoted to the real secret with a new expiry 90 days out third. A crash in between leaves a value that is in the vault and about to work, never one that works and is nowhere.Write a record
One row per attempt to the custom table: secret versions before and after, the trigger (age, a read, a missing secret, a resumed staging), never credential material. The workbook reads it.