Source: main (497baf0) Excluded: live tenant exports, generated artifacts, and dev-only tooling.
11 KiB
Agent Guidance: Intune / Entra Drift Backup
This repository tracks Git-based snapshots of Microsoft Intune and Entra ID configuration, generates review reports, and drives a rolling pull-request workflow for post-change review and remediation.
Project Overview
The implementation is centered on three Azure DevOps pipelines:
azure-pipelines.yml: daily full backup/export pipeline with rolling PR management (previously hourly; now driven primarily by event-driven change probe).azure-pipelines-review-sync.yml: 20-minute reviewer-decision sync and post-merge remediation queue.azure-pipelines-restore.yml: manual or auto-queued restore pipeline for approved baseline rollback.
Workflow at a high level:
- Export Intune and Entra configuration into
tenant-state/. - Generate Markdown/CSV reports in
tenant-state/reports/. - Filter known non-actionable drift noise before commit.
- Commit workload drift to
drift/intuneanddrift/entra. - Create or update one rolling PR per workload into
main. - Refresh the PR description with deterministic change/risk summary and optional Azure OpenAI narrative.
- Apply reviewer
/rejector/acceptdecisions and queue restore when needed.
Technology Stack
- Python 3: primary language for all automation scripts.
- Azure DevOps Pipelines: YAML-based CI/CD (
azure-pipelines.yml,azure-pipelines-review-sync.yml,azure-pipelines-restore.yml). - PowerShell & Bash: inline pipeline steps for Git operations, token retrieval, and conditional logic.
- IntuneCD (Python package, pinned to
2.5.0): exports Intune configuration and restores baseline state. - Microsoft Graph API: reads/writes tenant configuration and resolves references.
- Node.js / md-to-pdf (v5.2.5): generates HTML and PDF documentation artifacts from Markdown on full runs.
- Azure OpenAI: optional PR narrative generation.
Repository Layout
.
├── azure-pipelines.yml # Main backup pipeline (daily snapshot + event-driven trigger)
├── azure-pipelines-review-sync.yml # 20-minute review sync
├── azure-pipelines-restore.yml # Baseline restore pipeline
├── scripts/ # Python automation helpers
├── tests/ # unittest coverage for scripts
├── tenant-state/ # Committed JSON exports and reports
│ ├── intune/
│ ├── entra/
│ └── reports/
├── infra/ # Azure Function App (change probe)
│ └── change-probe/
├── deploy/ # Infrastructure provisioning scripts
├── docs/ # Security review docs and roadmap
├── md2pdf/ # HTML/PDF styling and configs
├── prod-as-built.md # Generated as-built source
└── README.md # Operational overview for humans
Key Scripts
export_entra_baseline.py: Graph API export for Entra objects (Named Locations, Authentication Strengths, Conditional Access, App Registrations, Enterprise Applications).commit_entra_drift.py: commits Entra drift with author attribution from audit logs.resolve_ca_references.py: resolves Conditional Access GUID references to human-readable names.filter_entra_enrichment_noise.py: reverts JSON churn caused by best-effort Graph enrichment (owners, app roles).filter_intune_partial_settings_noise.py: reverts partial Settings Catalog exports.generate_assignment_report.py: produces Markdown and CSV assignment inventories.generate_app_inventory_report.py: produces Entra apps inventory CSV.generate_object_inventory_reports.py: produces per-category object inventory CSVs.validate_backup_outputs.py: asserts required files exist after export.ensure_rolling_pr.py: creates or updates one rolling drift PR per workload.update_pr_review_summary.py: refreshes PR descriptions with change counts, risk assessment, and optional AI narrative.apply_reviewer_rejections.py: processes/rejectand/acceptreviewer thread commands.queue_post_merge_restore.py: queues restore pipeline after merged PRs that contained/rejectdecisions.probe_tenant_changes.py: polls Intune/Entra audit logs via Graph, implements debouncer (idle → armed → cooldown), and decides whether to trigger a backup.trigger_backup_pipeline.py: thin ADO REST API wrapper to queue the backup pipeline on demand.
Code Style and Conventions
- Every Python file starts with
#!/usr/bin/env python3andfrom __future__ import annotations. - Type hints are used throughout (
typing.Any,argparse.Namespace, etc.). - Internal helper functions are prefixed with
_. - Common environment parsing helpers appear in multiple scripts:
_env_text(name, default="")– reads and sanitizes env vars, treating unresolved Azure DevOps macros$(...)as empty._env_bool(name, default=False)– interprets1,true,yes,onas boolean true.
- Arguments use
argparsewith typed flags; pipeline variables are passed as env vars or CLI args. - JSON is written with
indent=4orindent=5andensure_ascii=False. - HTTP calls to Graph or Azure DevOps REST APIs use
urllib.request(no external HTTP library).
Testing
Tests are written with the Python standard library unittest framework. There is no pytest configuration (pyproject.toml, setup.py, or pytest.ini are absent). Modules are loaded dynamically in tests using importlib.util.spec_from_file_location so that scripts in scripts/ do not need to be on PYTHONPATH.
Run Tests
python3 -m unittest discover -s tests -v
Test Coverage Areas
test_ensure_rolling_pr.py: rolling PR creation, draft publishing, merge strategy logic.test_export_entra_baseline.py: Entra export parsing, concurrent export behavior, error handling.test_filter_entra_enrichment_noise.py: enrichment-only churn detection and reversion.test_filter_intune_partial_settings_noise.py: partial Settings Catalog export filtering.test_queue_post_merge_restore.py: post-merge restore queueing logic.test_update_pr_review_summary.py: semantic diffing, AI thread management, PR description upserts.test_validate_backup_outputs.py: validation rules for Intune and Entra outputs.
Build and Runtime Architecture
There is no traditional build step for the Python code. The pipelines install runtime dependencies on each run:
pip3 install "IntuneCD==2.5.0"
For local development, only a Python 3 interpreter is required; scripts use the standard library except for the optional IntuneCD package.
Change Probe (Event-Driven Backup Trigger)
Because Microsoft Graph change notifications and delta queries do not support Intune device management or Conditional Access resources, an audit-log polling architecture is used instead:
- Azure Function App (
infra/change-probe/):probe_timer: 5-minute timer trigger. Loads debouncer state from Azure Table Storage, runsprobe_tenant_changes.py, writes state back, and emits a queue message when the debouncer triggers.queue_consumer: queue trigger. Dequeues messages and callstrigger_backup_pipeline.pyto queue the ADO backup pipeline.
- Debouncer: 15-minute quiet window (idle → armed) + 30-minute cooldown. Prevents backup storms during bulk changes.
- State: stored in Azure Table Storage (
ProbeStatetable). - Provisioning:
deploy/provision-change-probe.ps1creates the Entra app, grants admin consent, provisions Resource Group / Storage Account / Function App, and configures app settings.
Pipeline Jobs
-
Intune backup job (
backup_intune):- Prepare
drift/intunebranch frommain. - Decide light vs full mode (configured full-run hour or
forceFullRun=true). - Run
IntuneCD-startbackup. - Filter partial Settings Catalog exports.
- Resolve assignment group names from Graph.
- Generate assignment and object inventory reports.
- Validate outputs.
- Commit drift and update rolling PR.
- Prepare
-
Entra backup job (
backup_entra):- Prepare
drift/entrabranch frommain. - Export selected categories with
export_entra_baseline.py. - Resolve Conditional Access references.
- Generate reports.
- Validate outputs.
- Filter enrichment noise and commit drift.
- Prepare
-
Review sync jobs (
sync_intune_review_decisions,sync_entra_review_decisions):- Apply
/rejectdecisions. - Update automated PR summary.
- Queue post-merge restore when needed.
- Apply
-
Restore job (
restore_from_baseline):- Checkout approved baseline snapshot (branch, tag, or commit).
- Prepare restore scope (
fullorselective). - Normalize payload JSON and strip display-only assignment labels.
- Run
IntuneCD-startupdatewith optional--entraupdate.
Security Considerations
- Token handling: Graph tokens are obtained via
Get-AzAccessTokenin PowerShell and passed as secret pipeline variables. Token payload is decoded to validate required application permissions before use. - Service connection: Azure DevOps service connection (e.g.
sc-astral-backup) uses workload federated credentials. - Permissions: read-only permissions for backup; read-write permissions (
...ReadWrite.All) for restore. Missing roles are surfaced as pipeline errors before any Graph mutations occur. - Path traversal: selective restore paths are normalized and validated against
..segments before file copy. - Dry run: restore pipeline defaults to
dryRun=trueand must be explicitly overridden to push changes. - Access token scope:
System.AccessTokenis required for PR and thread management via Azure DevOps REST APIs.
Common Development Tasks
Generate Entra export locally
python3 ./scripts/export_entra_baseline.py \
--root ./tenant-state/entra \
--token "$GRAPH_TOKEN" \
--enterprise-app-workers 8
Resolve Conditional Access references locally
python3 ./scripts/resolve_ca_references.py \
--root ./tenant-state/entra \
--token "$GRAPH_TOKEN"
Generate assignment report locally
python3 ./scripts/generate_assignment_report.py \
--root ./tenant-state/intune \
--output-dir ./tenant-state/reports/intune
Validate backup outputs locally
python3 ./scripts/validate_backup_outputs.py \
--workload intune \
--mode light \
--root ./tenant-state/intune \
--reports-root ./tenant-state/reports/intune
Key Environment / Pipeline Variables
BASELINE_BRANCH(default:main)DRIFT_BRANCH_INTUNE(default:drift/intune)DRIFT_BRANCH_ENTRA(default:drift/entra)BACKUP_FOLDER(default:tenant-state)ENABLE_WORKLOAD_INTUNE/ENABLE_WORKLOAD_ENTRAENABLE_PR_REVIEW_SUMMARY/ENABLE_PR_REVIEWER_DECISIONSAUTO_REMEDIATE_AFTER_MERGE/AUTO_REMEDIATE_DRY_RUNENABLE_PR_AI_SUMMARY+AZURE_OPENAI_ENDPOINT,AZURE_OPENAI_DEPLOYMENT,AZURE_OPENAI_API_KEYROLLING_PR_DELAY_REVIEWER_NOTIFICATIONS/ROLLING_PR_MERGE_STRATEGY
See the top of each pipeline YAML for the full variable list and defaults.