azure/azure-drift-detector
Detect configuration drift between deployed Azure resources and stored deployment state. Compare actual Azure configuration against desired state in .azure/deployments/, identify differences, and guide user through reconciliation options. Use when checking for manual changes, policy remediations, or unauthorized modifications.
npx skills add https://github.com/Azure/git-ape --skill azure-drift-detector
Detect when Azure resources have been modified outside of Git-Ape workflows (manual portal changes, Azure Policy remediations, external tools) and guide users through reconciliation.
Triggers:
Ask user which deployment to check for drift:
Which deployment do you want to check for drift?
Recent deployments:
- deploy-20260218-143022 (func-api-dev-eastus) - Deployed 2 hours ago
- deploy-20260217-091530 (app-webapp-prod-eastus) - Deployed 1 day ago
- deploy-20260216-153045 (sql-data-prod-eastus) - Deployed 2 days ago
Or enter deployment ID:
Load deployment state:
DEPLOYMENT_ID="deploy-20260218-143022"
DEPLOYMENT_PATH=".azure/deployments/$DEPLOYMENT_ID"
# Load stored state
REQUIREMENTS=$(cat "$DEPLOYMENT_PATH/requirements.json")
TEMPLATE=$(cat "$DEPLOYMENT_PATH/template.json")
METADATA=$(cat "$DEPLOYMENT_PATH/metadata.json")
# Extract resource IDs
RESOURCE_IDS=$(jq -r '.resources[].id' "$DEPLOYMENT_PATH/metadata.json")
For each resource in the deployment, fetch current configuration:
# Use Azure CLI to get current resource properties
for RESOURCE_ID in $RESOURCE_IDS; do
CURRENT_STATE=$(az resource show --ids "$RESOURCE_ID" --output json)
# Store for comparison
echo "$CURRENT_STATE" > "$DEPLOYMENT_PATH/drift-analysis/current-state-$(basename $RESOURCE_ID).json"
done
Alternative: Use Azure Resource Graph
# Query multiple resources efficiently
az graph query -q "
Resources
| where id in ('$RESOURCE_ID_1', '$RESOURCE_ID_2')
| project id, name, type, location, properties, tags
" --output json > "$DEPLOYMENT_PATH/drift-analysis/current-state.json"
Use the drift detection script to identify differences:
# Run drift detection
.github/skills/azure-drift-detector/scripts/detect-drift.sh \
--deployment-id "$DEPLOYMENT_ID" \
--output-format "markdown"
Script analyzes:
Drift categories:
Show user a comprehensive drift analysis:
## Drift Detection Report
**Deployment:** deploy-20260218-143022
**Checked:** 2026-02-18 19:30:00 UTC
**Resources Analyzed:** 5
### Summary
- 🔴 Critical Drift: 1 resource
- 🟡 Warning Drift: 2 resources
- ✅ No Drift: 2 resources
---
### 🔴 CRITICAL: Function App (func-api-dev-eastus)
**Property:** `properties.httpsOnly`
- Expected (deployment state): `true`
- Current (Azure): `false`
- **Impact:** Security vulnerability - HTTP traffic allowed
- **Changed:** ~30 minutes ago (2026-02-18 19:00:00 UTC)
- **Likely Cause:** Manual portal change or policy remediation
**Property:** `properties.siteConfig.appSettings.FUNCTIONS_WORKER_RUNTIME`
- Expected: `python`
- Current: `node`
- **Impact:** Runtime mismatch - application may fail
- **Changed:** ~30 minutes ago
---
### 🟡 WARNING: Storage Account (stfuncdeveastus8k3m)
**Property:** `tags.Environment`
- Expected: `dev`
- Current: `development`
- **Impact:** Tag inconsistency - reporting/billing may be affected
**Property:** `properties.minimumTlsVersion`
- Expected: `TLS1_2`
- Current: `TLS1_0`
- **Impact:** Weak TLS version - security concern
---
### ✅ NO DRIFT: Application Insights (appi-api-dev-eastus)
All properties match deployment state.
Present reconciliation options:
## Drift Reconciliation Options
You have 3 options for handling this drift:
### A. **Accept Drift** (Update IaC to match Azure)
Update deployment state files to reflect current Azure configuration.
- Updates: requirements.json, template.json, metadata.json
- Next deployment will use new values as baseline
- **Use when:** The Azure changes are intentional and should be preserved
### B. **Revert Drift** (Restore desired state from IaC)
Redeploy resources to enforce original configuration.
- Reverts: httpsOnly → true, runtime → python, tags → original
- Creates new deployment: deploy-20260218-193000-revert
- **Use when:** Azure changes were unauthorized or incorrect
### C. **Selective Reconciliation** (Choose per property)
Review each drift and decide individually:
- Keep some Azure changes
- Revert others to deployment state
- **Use when:** Some changes are valid, others are not
### D. **Mark as Known Drift** (Ignore for now)
Document the drift but don't reconcile yet.
- Adds to: drift-analysis/known-drift.json
- Future checks won't alert on these specific changes
- **Use when:** Investigating before deciding action
---
**Which option would you like?** (Type A, B, C, or D)
Option A: Accept Drift
# Update deployment state to match Azure
.github/skills/azure-drift-detector/scripts/accept-drift.sh \
--deployment-id "$DEPLOYMENT_ID"
# Actions:
# 1. Fetch current Azure state
# 2. Update requirements.json with new values
# 3. Regenerate ARM template to match
# 4. Update metadata.json with drift acceptance record
# 5. Create git commit (if in version control)
# Log acceptance
cat >> "$DEPLOYMENT_PATH/drift-analysis/drift-log.jsonl" <<EOF
{
"timestamp": "2026-02-18T19:30:00Z",
"action": "accept",
"user": "$(az account show --query user.name -o tsv)",
"changes": {
"httpsOnly": {"from": true, "to": false},
"runtime": {"from": "python", "to": "node"}
},
"reason": "Intentional runtime change to Node.js"
}
EOF
Option B: Revert Drift
# Redeploy to enforce desired state
.github/skills/azure-drift-detector/scripts/revert-drift.sh \
--deployment-id "$DEPLOYMENT_ID" \
--confirm
# Actions:
# 1. Load original template.json and parameters.json
# 2. Create revert deployment: deploy-{timestamp}-revert
# 3. Deploy with mode: Incremental (only changes)
# 4. Monitor deployment
# 5. Verify drift resolved
# 6. Update metadata with revert record
# Confirmation prompt:
echo "⚠️ This will revert the following changes:"
echo " - httpsOnly: false → true"
echo " - runtime: node → python"
echo ""
echo "Type 'confirm revert' to proceed:"
read CONFIRMATION
Option C: Selective Reconciliation
# Present each drift individually
echo "Drift 1/4: httpsOnly changed from true to false"
echo ""
echo "What should we do?"
echo " A. Keep Azure value (false) - Update IaC"
echo " B. Revert to IaC value (true) - Redeploy"
echo " S. Skip this property for now"
echo ""
echo "Your choice (A/B/S):"
read CHOICE_1
# Repeat for each drift
# Build reconciliation plan
# Execute combined update + revert
Option D: Mark as Known Drift
# Document known drift
cat >> "$DEPLOYMENT_PATH/drift-analysis/known-drift.json" <<EOF
{
"resource": "func-api-dev-eastus",
"properties": {
"httpsOnly": {
"expected": true,
"actual": false,
"markedBy": "[email protected]",
"markedAt": "2026-02-18T19:30:00Z",
"reason": "Investigating runtime issue, temporarily allowing HTTP",
"reviewBy": "2026-02-19T09:00:00Z"
}
}
}
EOF
# Future drift checks will skip these properties
Scheduled Drift Detection:
# Add to cron or Azure DevOps pipeline
# Run daily drift check on all active deployments
.github/scripts/drift-check-all.sh
# Output:
# Checking 15 active deployments...
# ✅ 12 deployments: No drift detected
# 🟡 2 deployments: Warning drift (tags only)
# 🔴 1 deployment: Critical drift (security settings)
#
# Details in: .azure/drift-reports/2026-02-18.md
Pre-Deployment Drift Check:
User: @git-ape deploy changes to func-api-dev-eastus
Agent: Before deploying, let me check for configuration drift...
🔴 DRIFT DETECTED
The current Azure state differs from your last deployment.
Changes detected:
- httpsOnly: true → false (in Azure)
- tags.Environment: dev → development (in Azure)
What would you like to do?
A. Accept drift first, then deploy your changes on top
B. Revert drift, then deploy (restore original baseline)
C. Cancel deployment and investigate drift manually
This ensures we don't lose Azure changes or create conflicts.
Azure Policy: "All Function Apps must use managed identity"
Policy remediation adds:
- identity.type: SystemAssigned
Drift detection shows:
🟡 WARNING: Managed identity added by Azure Policy
Recommendation: **Accept drift** - This is a policy-enforced improvement.
Update IaC to include managed identity in future deployments.
Production incident: Function App scale limit reached
Manual portal change:
- properties.siteConfig.alwaysOn: true
- App Service Plan: Scaled from S1 to S2
Drift detection shows:
🔴 CRITICAL: SKU changed, Always On enabled
Recommendation: **Accept drift + Document** - Emergency change required.
Update IaC and create proper change request for next deployment.
Unknown change detected:
- properties.httpsOnly: true → false
- Changed: 2 days ago
- No change ticket or approved request
Drift detection shows:
🔴 CRITICAL: HTTPS enforcement disabled
Recommendation: **Revert drift immediately** - Security violation.
Investigate who made the change and why. Restore secure state.
Billing team added cost center tags:
- tags.CostCenter: "CC-12345"
- tags.BillingOwner: "[email protected]"
Drift detection shows:
🟡 WARNING: New tags added
Recommendation: **Accept drift** - Valid tags for financial tracking.
Update IaC baseline to preserve these tags in future deployments.
GitHub Action Example:
# .github/workflows/drift-detection.yml
name: Azure Drift Detection
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch: # Manual trigger
jobs:
detect-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Run Drift Detection
run: |
.github/scripts/drift-check-all.sh --format github-annotation
- name: Create Issue if Critical Drift
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '🔴 Critical Configuration Drift Detected',
body: 'See workflow logs for details',
labels: ['drift', 'security']
})
Save drift analysis to:
.azure/deployments/{deployment-id}/drift-analysis/
├── current-state.json # Azure resource state at check time
├── drift-report.md # Human-readable report
├── drift-details.json # Machine-readable diff
├── known-drift.json # Acknowledged drift to ignore
└── drift-log.jsonl # Audit log of reconciliation actions
Take azure/azure-drift-detector from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.