Source: main (497baf0) Excluded: live tenant exports, generated artifacts, and dev-only tooling.
16 KiB
Intune / Entra Drift Backup
This repository keeps Git-tracked snapshots of Microsoft Intune and selected Entra ID configuration, generates review reports, and drives a rolling pull-request workflow for post-change review and remediation.
Product name: ASTRAL (Admin Security Through Review, Automation & Least-privilege)
Getting Started
This repository is designed to be forked or downloaded into your own Azure DevOps organization. Each tenant gets its own project and pipeline instance.
Quick start:
- Fork or import this repository into an Azure DevOps project.
- Review
templates/variables-tenant.ymland create a matching Azure DevOps Variable Group in your project (e.g.vg-astral-tenant). - Uncomment the variable group reference in the three pipeline YAMLs.
- Run
deploy/provision-change-probe.ps1to create the Azure AD app registration, assign Graph permissions, configure the federated credential, and optionally provision the event-driven change probe (Azure Function App). - Create the Azure DevOps service connection using the app registration details from the bootstrap script.
- Import the three pipelines (
azure-pipelines.yml,azure-pipelines-review-sync.yml,azure-pipelines-restore.yml) into Azure DevOps. - Run
deploy/validate-deployment.ymlto verify connectivity and permissions. - Set
AUTO_REMEDIATE_RESTORE_PIPELINE_IDin your variable group after the restore pipeline is imported.
See deploy/onboarding-runbook.md for the full step-by-step guide.
What The Repository Does
The implementation is centered on three Azure DevOps pipelines:
azure-pipelines.yml: backup/export pipeline with rolling PR management. Runs daily at 02:00 to generate a full tenant snapshot, reports, and documentation artifacts, and is also triggered on-demand by the 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.
The main workflow is:
- 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.
An event-driven change probe monitors Intune and Entra audit logs and triggers the backup pipeline when actual changes are detected, replacing the previous hourly polling model with a responsive event-driven approach.
This is an ex-post change-management model: admins can change settings in the Microsoft admin portals, and the repo turns those changes into auditable Git drift with a review and rollback path.
Current Baseline Coverage
Intune currently tracks:
- App Configuration
- App Protection
- Apple Push Notification
- Apple VPP Tokens
- Applications
- Compliance Policies
- Device Configurations
- Device Management Settings
- Enrollment Configurations
- Enrollment Profiles
- Filters
- Scope Tags
- Scripts
- Settings Catalog
Entra currently tracks:
- Named Locations
- Authentication Strengths
- Conditional Access
- App Registrations
- Enterprise Applications
Current scope behavior:
- Named Locations, Authentication Strengths, and Conditional Access run on hourly light runs and midnight full runs.
- App Registrations and Enterprise Applications are enabled in the pipeline but exported only on full runs.
- During light runs, the previous drift-branch snapshot of
App RegistrationsandEnterprise Applicationsis preserved to avoid churn and heavy export cost.
Repository Layout
README.md: operational overview.azure-pipelines.yml: backup/export, report generation, drift commit, rolling PR, and docs/artifact flow.azure-pipelines-review-sync.yml: reviewer decision sync and post-merge remediation helper.azure-pipelines-restore.yml: baseline restore pipeline with full or selective scope.infra/change-probe/: Azure Function App for event-driven change detection.deploy/provision-change-probe.ps1: unified provisioning script for the change probe infrastructure.docs/m365-baseline-roadmap.md: expansion roadmap beyond current workload scope.docs/security-review-package.md: implementation-focused security review package.docs/security-review-questionnaire.md: short-form security review answers.scripts/: export, reporting, PR automation, validation, remediation helpers, and change probe logic.tests/: focused unit coverage for the Python helpers.tenant-state/intune: committed Intune JSON export.tenant-state/entra: committed Entra JSON export.tenant-state/reports/intune: Intune CSV/Markdown reports.tenant-state/reports/entra: Entra CSV/Markdown reports.prod-as-built.md: generated as-built document source.md2pdf/: HTML/PDF styling and config for documentation publish.
Pipeline Model
Main Backup Pipeline
azure-pipelines.yml runs daily at 02:00 on main to generate a full tenant snapshot, reports, and documentation artifacts. It is also triggered on-demand by the change probe when drift is detected.
For Intune it:
- Prepares
drift/intunefrommain. - Chooses light vs full mode from the configured local timezone, with
forceFullRun=trueoverride. - Runs IntuneCD export.
- Reverts partial Settings Catalog exports with
scripts/filter_intune_partial_settings_noise.py. - Resolves assignment group names from Graph when needed.
- Generates assignment and object inventory reports.
- Validates outputs with
scripts/validate_backup_outputs.py. - Commits drift and updates the rolling PR flow.
For Entra it:
- Prepares
drift/entrafrommain. - Chooses effective export scope per mode.
- Exports selected categories with
scripts/export_entra_baseline.py. - Resolves Conditional Access reference names with
scripts/resolve_ca_references.py. - Generates assignment, app, and object inventory reports.
- Validates outputs with
scripts/validate_backup_outputs.py. - Reverts enrichment-only JSON churn with
scripts/filter_entra_enrichment_noise.py. - Commits drift with
scripts/commit_entra_drift.py.
Review Sync Pipeline
azure-pipelines-review-sync.yml runs every 20 minutes on main and exists to shorten the reviewer feedback loop.
Per workload it can:
- apply reviewer
/rejectand/acceptdecisions withscripts/apply_reviewer_rejections.py - refresh the automated PR summary
- queue restore after merged PRs that contained reviewer
/rejectdecisions usingscripts/queue_post_merge_restore.py
Restore Pipeline
azure-pipelines-restore.yml restores from approved baseline (main by default) or from a historical branch, tag, or commit.
Supported restore modes:
full: restore the full committed Intune baselineselective: restore only selected file paths
It also supports optional Entra update when restore automation is triggered for Entra review outcomes.
Schedule And Run Modes
- Main backup schedule: daily at 02:00,
0 2 * * *, onmain(full snapshot, reports, and docs) - Change probe trigger: event-driven, on-demand via Azure Function App
- Review sync schedule: every 20 minutes,
*/20 * * * *, onmain - Full mode: configured full-run hour (default 00:00) or manual queue with
forceFullRun=true - Light mode: all probe-triggered runs except the daily full run
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:
probe_timer(5-minute timer trigger): polls Intune and Entra audit logs via Microsoft Graph, evaluates a debouncer state machine (idle → armed → cooldown), and emits a queue message when the quiet window elapses.queue_consumer(queue trigger): dequeues messages and calls the Azure DevOps REST API to queue the backup pipeline.- Debouncer: 15-minute quiet window + 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.
Full mode adds:
- full Entra scope, including App Registrations and Enterprise Applications
- Intune split-documentation generation
- HTML/PDF artifact generation when browser dependencies are available
- optional tagging and documentation publish steps
Branch And PR Model
- Baseline branch:
main - Drift branches:
drift/intunedrift/entra
Each workload keeps one rolling PR open to main.
Key behavior:
- reports are generated in
tenant-state/reports/*but excluded from rolling drift commits and PR diffs - rolling PRs can be created as draft first, then published after automated summary generation when
ROLLING_PR_DELAY_REVIEWER_NOTIFICATIONS=true - merge strategy for the rolling PR is controlled by
ROLLING_PR_MERGE_STRATEGYand defaults torebase
Review, Tickets, And Remediation
The PR automation currently supports:
- deterministic operation counts and risk assessment
- rename-aware semantic comparison
- stable change fingerprinting for idempotent summary refresh
- optional Azure OpenAI reviewer narrative
- optional per-file
Change Neededreview threads whenREQUIRE_CHANGE_TICKETS=true
Reviewer thread commands:
/reject: remove that file-level drift from the rolling PR by resetting it to baseline/accept: keep that file in PR scope
Supported remediation paths:
- Reject and abandon a whole rolling PR. The next run can detect the matching rejected snapshot and queue restore automatically.
- Reject selected files in ticket threads, then merge the accepted remainder. The review-sync pipeline can queue restore after merge so the tenant is reconciled to the merged baseline.
- Queue
azure-pipelines-restore.ymlmanually for full or selective historical rollback.
Key Variables
Core repo and branch settings:
BASELINE_BRANCHDRIFT_BRANCH_INTUNEDRIFT_BRANCH_ENTRAROLLING_PR_TITLE_INTUNEROLLING_PR_TITLE_ENTRABACKUP_FOLDERINTUNE_BACKUP_SUBDIRENTRA_BACKUP_SUBDIRREPORTS_SUBDIR
Workload toggles:
ENABLE_WORKLOAD_INTUNEENABLE_WORKLOAD_ENTRAENABLE_ENTRA_CONDITIONAL_ACCESS
Intune behavior:
INTUNECD_VERSIONEXCLUDE_SCRIPT_BACKUPINTUNE_EXCLUDE_CSVSPLIT_DOCUMENTATION
Entra behavior:
ENTRA_INCLUDE_NAMED_LOCATIONSENTRA_INCLUDE_AUTHENTICATION_STRENGTHSENTRA_INCLUDE_CONDITIONAL_ACCESSENTRA_INCLUDE_APP_REGISTRATIONSENTRA_INCLUDE_ENTERPRISE_APPSENTRA_ENTERPRISE_APP_WORKERS
PR and reviewer automation:
ENABLE_PR_REVIEW_SUMMARYENABLE_PR_REVIEWER_DECISIONSROLLING_PR_DELAY_REVIEWER_NOTIFICATIONSROLLING_PR_MERGE_STRATEGYREQUIRE_CHANGE_TICKETSCHANGE_TICKET_REGEXDEBUG_CHANGE_TICKET_THREADS
Auto-remediation:
AUTO_REMEDIATE_ON_PR_REJECTIONAUTO_REMEDIATE_AFTER_MERGEAUTO_REMEDIATE_AFTER_MERGE_LOOKBACK_HOURSAUTO_REMEDIATE_RESTORE_PIPELINE_IDAUTO_REMEDIATE_DRY_RUNAUTO_REMEDIATE_UPDATE_ASSIGNMENTSAUTO_REMEDIATE_REMOVE_OBJECTSAUTO_REMEDIATE_MAX_WORKERSAUTO_REMEDIATE_EXCLUDE_CSV
Change probe settings:
PROBE_APP_IDPROBE_APP_SECRETPROBE_QUIET_WINDOW_MINUTES(default: 15)PROBE_COOLDOWN_MINUTES(default: 30)GRAPH_TOKEN(optional passthrough)
Azure OpenAI integration:
ENABLE_PR_AI_SUMMARYAZURE_OPENAI_ENDPOINTAZURE_OPENAI_DEPLOYMENTAZURE_OPENAI_API_KEYAZURE_OPENAI_API_VERSIONPR_AI_PAYLOAD_MAX_BYTESPR_AI_MAX_TOKENSPR_AI_COMPACT_MAX_CHARS
Required Azure DevOps Permissions
The pipeline build identity should have repository permissions to:
- contribute
- create branch
- force push
- create and update pull requests
- create tag if tagging is enabled
For auto-queued restore, the same identity also needs on azure-pipelines-restore.yml:
View buildsQueue builds- pipeline authorization if explicit pipeline permissions are enforced
Also enable script access to System.AccessToken.
Required Microsoft Graph Application Permissions
Baseline read permissions used by the current implementation:
Device.Read.AllDeviceManagementApps.Read.AllDeviceManagementConfiguration.Read.AllDeviceManagementManagedDevices.Read.AllDeviceManagementRBAC.Read.AllDeviceManagementScripts.Read.AllDeviceManagementServiceConfig.Read.AllGroup.Read.AllPolicy.Read.AllPolicy.Read.ConditionalAccessPolicy.Read.DeviceConfigurationUser.Read.All
Additional read permissions used by the current Entra scope:
Application.Read.AllRoleManagement.Read.DirectoryorDirectory.Read.Allfor richer name resolutionAuditLog.Read.Allfor best-effort Entra drift author attribution
Restore pipeline write permissions:
DeviceManagementApps.ReadWrite.AllDeviceManagementConfiguration.ReadWrite.AllDeviceManagementManagedDevices.ReadWrite.AllDeviceManagementRBAC.ReadWrite.AllDeviceManagementScripts.ReadWrite.AllDeviceManagementServiceConfig.ReadWrite.AllGroup.Read.All
Additional restore permission when includeEntraUpdate=true:
Policy.Read.AllPolicy.ReadWrite.ConditionalAccess
Outputs
Intune outputs:
- JSON backup under
tenant-state/intune/** tenant-state/reports/intune/policy-assignments.mdtenant-state/reports/intune/policy-assignments.csvtenant-state/reports/intune/object-inventory-all.csvtenant-state/reports/intune/Object Inventory/*-inventory.csv
Entra outputs:
- JSON backup under
tenant-state/entra/** tenant-state/reports/entra/policy-assignments.mdtenant-state/reports/entra/policy-assignments.csvtenant-state/reports/entra/apps-inventory.csvtenant-state/reports/entra/object-inventory-all.csvtenant-state/reports/entra/Object Inventory/*-inventory.csv
Full-run documentation artifacts:
prod-as-built-split-markdownprod-as-built-split-htmlprod-as-built-split-pdf
Local Script Usage
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:
python3 ./scripts/resolve_ca_references.py \
--root ./tenant-state/entra \
--token "$GRAPH_TOKEN"
Generate Intune assignment report:
python3 ./scripts/generate_assignment_report.py \
--root ./tenant-state/intune \
--output-dir ./tenant-state/reports/intune
Generate Entra assignment report:
python3 ./scripts/generate_assignment_report.py \
--root ./tenant-state/entra \
--output-dir ./tenant-state/reports/entra
Generate Entra apps inventory:
python3 ./scripts/generate_app_inventory_report.py \
--root ./tenant-state/entra \
--output-dir ./tenant-state/reports/entra
Generate workload object inventories:
python3 ./scripts/generate_object_inventory_reports.py \
--root ./tenant-state/intune \
--output-dir ./tenant-state/reports/intune
python3 ./scripts/generate_object_inventory_reports.py \
--root ./tenant-state/entra \
--output-dir ./tenant-state/reports/entra
Validate backup outputs:
python3 ./scripts/validate_backup_outputs.py \
--workload intune \
--mode light \
--root ./tenant-state/intune \
--reports-root ./tenant-state/reports/intune
Run the change probe locally:
python3 ./scripts/probe_tenant_changes.py \
--app-id "$PROBE_APP_ID" \
--app-secret "$PROBE_APP_SECRET" \
--tenant-id "$TENANT_ID" \
--state-file ./probe-state.json \
--output ./probe-result.json
Trigger the backup pipeline manually:
python3 ./scripts/trigger_backup_pipeline.py \
--organization cqre \
--project Intune \
--pipeline-id 1 \
--token "$ADO_TOKEN" \
--branch refs/heads/main
Tests
The repository includes focused unit tests for:
- Entra export behavior
- backup output validation
- rolling PR creation/update logic
- PR summary generation
- reviewer rejection processing
- post-merge restore queueing
- Intune partial-export noise filtering
- Entra enrichment-noise filtering