Server-side Chrome policy delivery for Entra IDβonly (Azure AD joined) devices β bypassing the Group Policy dependency that breaks ADMX-based Chrome settings on cloud-managed endpoints.
Chrome policies deployed via Intune Settings Catalog (ADMX-backed) fail silently on Entra IDβonly joined devices. This happens because:
- GP Notification dependency β Chrome's
PolicyLoaderWincallsRegisterGPNotification()which requires a domain-joined machine - Domain join gate β
mdm_utils.ccchecksIsEnrolledToDomain()before applying policies - ADMX registry mirroring β Intune writes to
HKLM:\SOFTWARE\Microsoft\PolicyManager\providers\...but the GP Client Service never mirrors them toHKLM:\SOFTWARE\Policies\Google\Chromeon cloud-only devices
This affects ALL Chrome policies equally on cloud-only joined devices β not just specific ones.
Chrome Policy Manager implements a server-side policy resolution engine that delivers Chrome policies directly to device registries via Intune Proactive Remediation scripts, completely bypassing the broken GP pipeline.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ARCHITECTURE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββ βββββββββββββ ββββββββββββββββββββββββββββ β
β β Admin βββββΆβ REST API ββββββ Intune Remediation β β
β β UI β β (.NET 9) β β (PowerShell scripts) β β
β β (Blazor) β βββββββ¬ββββββ ββββββββββββββββββββββββββββ β
β ββββββββββββ β β
β ββββββ΄βββββ β
β β SQL DB β β PolicySets, Versions, β
β β (S2) β Assignments, DeviceState β
β ββββββ¬βββββ β
β β β
β βββββββββββΌββββββββββ β
β β β β β
β ββββββ΄βββββ ββββ΄ββββ ββββ΄βββββββββββ β
β β MS Graphβ β Svc β β Graph Change β β
β β (delta) β β Bus β β Webhooks β β
β βββββββββββ ββββββββ βββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Feature | Description |
|---|---|
| ADMX Catalog Ingestion | Parse Chrome ADMX/ADML templates β browse 700+ policies with descriptions, types, categories |
| PolicySet Versioning | Immutable versions (Draft β Active β Archived) with hash-based change detection |
| Group-Based Targeting | Assign policies to Entra ID security groups with priority-based conflict resolution |
| Mandatory & Recommended | Support both Chrome policy scopes per assignment |
| Effective Policy Resolution | Server resolves device β groups β assignments β merged settings (lower priority wins) |
| Device Observability | Real-time compliance dashboard, offline detection, error tracking |
| Intune Delivery | Proactive Remediation hourly check β detect drift β apply policies via registry |
| Audit Trail | Full audit logging for all policy changes and device interactions |
ChromePolicyManager/
βββ src/
β βββ Server/
β β βββ ChromePolicyManager.Api/ # REST API (.NET 9 Minimal API)
β β β βββ Data/ # EF Core DbContext + models
β β β βββ Endpoints/ # Policy, Assignment, Device, Catalog, Monitoring
β β β βββ Models/ # PolicySet, Version, Assignment, CatalogEntry
β β β βββ Services/ # AdmxParser, EffectivePolicy, Graph, Reporting
β β βββ ChromePolicyManager.Admin/ # Blazor Server Admin UI (MudBlazor)
β β βββ Components/Pages/ # Dashboard, Catalog, Policies, Assignments, Devices
β βββ Client/
β βββ Detect-ChromePolicy.ps1 # Intune detection script
β βββ Remediate-ChromePolicy.ps1 # Intune remediation script
βββ infra/
β βββ Deploy-Infrastructure.ps1 # One-click Azure deployment
βββ tools/ # ADMX template downloads (gitignored)
- .NET 9 SDK
- Azure subscription (with Intune license for remediation)
azCLI authenticatedghCLI (optional, for repo operations)
cd infra
.\Deploy-Infrastructure.ps1This creates: Resource Group, SQL Server (Entra-only auth), App Service Plan (B1), Web Apps (API + Admin), Key Vault, Service Bus, App Configuration.
Download the Chrome ADMX templates and upload via the Admin UI or API:
# Via API (multipart upload)
curl -X POST https://your-api.azurewebsites.net/api/catalog/import \
-F "admxZip=@policy_templates.zip" \
-F "version=136.0"Use the Admin UI at https://your-admin.azurewebsites.net/catalog to:
- Browse the catalog β filter by category/type β view descriptions
- Select policies and configure values
- Create PolicySets (e.g., "Security Baseline", "User Experience")
- Add versions with specific settings
- Assign to Entra ID groups with priority
The deployment script automatically creates a Proactive Remediation in Intune that:
- Runs hourly on targeted devices
- Detects drift by comparing local policy hash vs server hash
- Remediates by writing Chrome registry policies directly to
HKLM:\SOFTWARE\Policies\Google\Chrome
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/catalog |
Browse policy catalog (filter: ?category=&search=&dataType=&recommended=) |
GET |
/api/catalog/categories |
List available categories |
GET |
/api/catalog/stats |
Import statistics |
POST |
/api/catalog/import |
Import ADMX zip (multipart/form-data) |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/policies |
List all PolicySets with versions |
POST |
/api/policies |
Create new PolicySet |
POST |
/api/policies/{id}/versions |
Add version with settings JSON |
POST |
/api/policies/versions/{id}/promote |
Promote Draft β Active |
POST |
/api/policies/{id}/rollback/{versionId} |
Rollback to previous version |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/assignments |
List all assignments |
POST |
/api/assignments |
Create group assignment (priority + scope) |
DELETE |
/api/assignments/{id} |
Remove assignment |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/devices/{id}/effective-policy |
Resolve effective policy for device |
POST |
/api/devices/{id}/report |
Device reports compliance status |
GET |
/api/monitoring/dashboard |
Compliance dashboard data |
GET |
/api/monitoring/offline |
Offline devices (>N hours) |
GET |
/api/monitoring/errors |
Devices with errors |
GET |
/health |
Health check |
Client Device β API: "What policies apply to me?" (GET /devices/{id}/effective-policy)
β
βΌ
MS Graph: devices/{id}/memberOf β [Group1, Group2, ...]
β
βΌ
Match groups β Active PolicyAssignments
β
βΌ
Sort by Priority (ascending: lower = higher priority)
β
βΌ
Merge settings (first-writer-wins per key, separated by scope)
β
βΌ
Return: { mandatory: {...}, recommended: {...}, hash: "abc123" }
- Intune ADMX ingestion writes to
HKLM\SOFTWARE\Microsoft\PolicyManager\providers\{GUID}\... - This relies on GP Client Service to mirror to
HKLM\SOFTWARE\Policies\Google\Chrome - On Entra IDβonly devices, GP Client mirroring is broken (no
RegisterGPNotification, no domain join) - Chrome reads only from
HKLM\SOFTWARE\Policies\Google\Chromeβ policies never arrive
- Chrome's
PolicyLoaderWinreadsHKLM\SOFTWARE\Policies\Google\Chromeunconditionally - No domain-join check gates registry policy reading
- Chrome polls registry every 15 minutes (
kReloadInterval = base::Minutes(15)) - Entra IDβonly devices get
FULLY_TRUSTEDmanagement authority β no policy filtering
PolicyLoaderWin::InitOnBackgroundThread()β requiresRegisterGPNotification()successmdm_utils.cc::IsEnrolledToDomain()β GP registry path check fails on cloud-only devicesWinGPOListProviderβ depends on Active Directory infrastructure not present on Entra-only devices
Device-facing endpoints are protected by Azure API Management acting as a security gateway. The backend API never receives unauthenticated device traffic.
ββββββββββββββββ OAuth2 Token ββββββββββββββββ Managed Identity ββββββββββββββββ
β Device β ββββββββββββββββββββΆ β APIM β βββββββββββββββββββββΆ β Backend API β
β (PowerShell) β β Gateway β β (.NET 9) β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β
βββββββ΄ββββββ
β Validates β
β device JWT β
β Rate limitsβ
β Strips β
β spoofable β
β headers β
βββββββββββββ
Security flow:
- Device acquires OAuth2 token (client credentials + device certificate) from Entra ID
- APIM validates JWT (issuer, audience, expiry, required claims)
- APIM rate-limits per device identity (30 calls/hour/device)
- APIM strips any client-supplied identity headers (anti-spoofing)
- APIM sets trusted
X-Forwarded-Device-Idfrom validated JWT claims - APIM authenticates to backend using its managed identity (no shared secrets)
- Backend middleware verifies the request's
appidclaim matches APIM's identity
Separation of concerns:
| Layer | Responsibility |
|---|---|
| APIM | Device auth, rate limiting, DDoS protection, request logging |
| Backend | Business logic, policy resolution, database, Graph API |
| Admin UI | Direct Entra ID JWT auth (doesn't go through APIM) |
- Entra ID authentication for Admin UI (interactive login)
- Managed Identity for all Azure resource access (no stored credentials)
- Key Vault for secrets (connection strings, Graph client secret)
- Entra-only SQL auth (no SQL passwords β MCAPS compliant)
- Audit logging for all policy changes and device interactions
- CORS restricted to Admin UI origin only
- Service Bus for async device report processing (202 Accepted pattern)
- Backend restriction: device endpoints reject calls not originating from APIM
The solution is designed to handle large-scale enterprise environments (100,000+ devices) with minimal infrastructure cost. Three key optimizations make this possible:
The GET /devices/{id}/effective-policy endpoint returns an ETag header containing the policy hash. On subsequent requests, the client sends If-None-Match with its cached hash:
Client β API: GET /effective-policy (If-None-Match: "abc123")
API β Client: 304 Not Modified β No body, minimal compute
Only when policy actually changes:
Client β API: GET /effective-policy (If-None-Match: "abc123")
API β Client: 200 OK + full payload (ETag: "def456")
Impact: At 100k devices/hour with ~90% steady state β only ~10k full responses/hour carry a payload.
Instead of calling Microsoft Graph for every device check-in (which would hit throttling limits at scale), the API subscribes to real-time webhook notifications for group membership changes:
βββββββββββββββ Webhook: "Group X changed" βββββββββββββββ
β Microsoft β βββββββββββββββββββββββββββββββββββΆ β CPM API β
β Graph β β (marks β
βββββββββββββββ β group as β
β dirty) β
ββββββββ¬βββββββ
β
Device check-in: βΌ
- Device in Group X β Graph call (real-time, fresh data)
- Device in Group Y (unchanged) β use cached membership
Implementation:
GroupChangeNotificationService(BackgroundService) maintains subscriptions for all groups used in policy assignments- Subscriptions auto-renew before the 4230-minute Graph limit (~3 days)
/api/webhooks/group-changereceives notifications and marks affected groupsWebhookEndpoints.HasGroupChanged()allows the effective policy resolver to skip Graph calls for unchanged groups
Impact: Reduces Graph API calls from 100,000/hour to ~50-100/hour (only devices in groups that actually changed), while maintaining zero-latency reactivity β policy changes propagate within minutes, not hours like Intune.
Upgraded from Basic (5 DTU) to Standard S2 to handle sustained write throughput:
- 100k device reports/hour = ~28 writes/sec sustained
- S2 provides 50 DTU β comfortable headroom for reads + writes + indexes
| Metric | Without optimizations | With optimizations |
|---|---|---|
| Graph API calls/hour | 100,000 (throttled) | 50-100 |
| Full policy responses/hour | 100,000 | ~10,000 |
| Network bandwidth/hour | ~500 MB | ~50 MB |
| SQL write pressure | 100k full reports | 100k lightweight + 10k full |
| Reactivity | N/A (was polling) | Real-time (webhook push) |
| Component | SKU | Monthly Cost (est.) |
|---|---|---|
| App Service | S2 or P1v3 | β¬70-140 |
| Azure SQL | S2 (50 DTU) | β¬60-150 |
| Service Bus | Standard | β¬10 |
| Total | ~β¬150-300/month |
| Component | Technology |
|---|---|
| API | .NET 9, Minimal API, Entity Framework Core |
| Admin UI | Blazor Server, MudBlazor 8 |
| Database | Azure SQL S2, 50 DTU (Entra-only auth) |
| Auth | Microsoft Identity Web, MSAL, Device Certificates |
| Group Resolution | Microsoft Graph SDK + Change Notifications |
| Messaging | Azure Service Bus (async device reports) |
| Config | Azure App Configuration (Standard) |
| Secrets | Azure Key Vault |
| Hosting | Azure App Service (B1 β S2 at scale) |
| Client | PowerShell 5.1 (Intune Proactive Remediation) |
| Policy Catalog | Chrome ADMX/ADML parser (700+ policies) |
- Fork the repo
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes
- Push to the branch
- Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
chrome-policy, intune, entra-id, azure-ad-joined, admx, group-policy-workaround, chrome-enterprise, mdm, proactive-remediation, browser-management, endpoint-management, registry-policy, blazor, dotnet, azure
Built to solve a real-world enterprise pain point β Chrome policy delivery on modern cloud-only managed devices where ADMX-based Settings Catalog fails silently.