Skip to content

robgrame/ChromePolicyManager

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

64 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Chrome Policy Manager

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.

.NET 10 Azure Intune License: MIT

🎯 The Problem

Chrome policies deployed via Intune Settings Catalog (ADMX-backed) fail silently on Entra ID–only joined devices. This happens because:

  1. GP Notification dependency β€” Chrome's PolicyLoaderWin calls RegisterGPNotification() which requires a domain-joined machine
  2. Domain join gate β€” mdm_utils.cc checks IsEnrolledToDomain() before applying policies
  3. ADMX registry mirroring β€” Intune writes to HKLM:\SOFTWARE\Microsoft\PolicyManager\providers\... but the GP Client Service never mirrors them to HKLM:\SOFTWARE\Policies\Google\Chrome on cloud-only devices

This affects ALL Chrome policies equally on cloud-only joined devices β€” not just specific ones.

πŸ’‘ The Solution

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     β”‚                   β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

✨ Key Features

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
Device Log Ingestion Centralized log collection from detection/remediation scripts with level filtering
Policy Validation Schema-based validation of policy values against known Chrome policy types
Intune Delivery Proactive Remediation hourly check β†’ detect drift β†’ apply policies via registry
Inline Remediation Detection script optionally remediates drift without waiting for Intune remediation cycle
Push Remediation Trigger Optional Windows-style push: cycle assignment group devices and invoke on-demand remediation commands (batched)
Audit Trail Full audit logging for all policy changes and device interactions

πŸ—οΈ Project Structure

ChromePolicyManager/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Server/
β”‚   β”‚   β”œβ”€β”€ ChromePolicyManager.Api/        # REST API (.NET 10 Minimal API)
β”‚   β”‚   β”‚   β”œβ”€β”€ Data/                       # EF Core DbContext + models
β”‚   β”‚   β”‚   β”œβ”€β”€ Endpoints/                  # Policy, Assignment, Device, Catalog, Monitoring, Webhook
β”‚   β”‚   β”‚   β”œβ”€β”€ Middleware/                 # APIM Gateway authentication middleware
β”‚   β”‚   β”‚   β”œβ”€β”€ Models/                     # PolicySet, Version, Assignment, CatalogEntry, DeviceLog
β”‚   β”‚   β”‚   └── Services/                   # AdmxParser, EffectivePolicy, Graph, Reporting, Validator
β”‚   β”‚   └── ChromePolicyManager.Admin/      # Blazor Server Admin UI (MudBlazor)
β”‚   β”‚       └── Components/Pages/           # Dashboard, Catalog, Policies, Assignments, Devices
β”‚   └── Client/
β”‚       β”œβ”€β”€ Detect-ChromePolicy.ps1         # Intune detection script (supports inline remediation)
β”‚       └── Remediate-ChromePolicy.ps1      # Intune remediation script
β”œβ”€β”€ infra/
β”‚   β”œβ”€β”€ Deploy-Infrastructure.ps1           # One-click Azure deployment
β”‚   β”œβ”€β”€ main.bicep                          # Infrastructure-as-Code (Bicep)
β”‚   β”œβ”€β”€ main.bicepparam                     # Bicep parameters
β”‚   └── apim/                               # API Management policies
└── tools/                                  # ADMX template downloads (gitignored)

πŸš€ Quick Start

Prerequisites

  • .NET 10 SDK
  • Azure subscription (with Intune license for remediation)
  • az CLI authenticated
  • gh CLI (optional, for repo operations)

1. Deploy Infrastructure

cd infra
.\Deploy-Infrastructure.ps1

This creates: Resource Group, SQL Server (Entra-only auth), App Service Plan (B1), Web Apps (API + Admin), Key Vault, Service Bus, API Management (Developer SKU), App Configuration.

Note: The default Bicep deployment uses cost-optimized SKUs (SQL Basic 5 DTU, Service Bus Basic, App Configuration Free). For production at scale (100k+ devices), upgrade to the recommended SKUs listed in the Scaling section.

2. Import Chrome Policy Catalog

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"

3. Create Policy Sets

Use the Admin UI at https://your-admin.azurewebsites.net/catalog to:

  1. Browse the catalog β†’ filter by category/type β†’ view descriptions
  2. Select policies and configure values
  3. Create PolicySets (e.g., "Security Baseline", "User Experience")
  4. Add versions with specific settings
  5. Assign to Entra ID groups with priority

4. Deploy Intune Remediation

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
  • Supports inline remediation in the detection script (configurable via $EnableInlineRemediation) for faster drift correction without waiting for Intune's remediation cycle

πŸ”§ API Endpoints

Policy Catalog

Method Endpoint Description
GET /api/catalog Browse policy catalog (filter: ?category=&search=&dataType=&recommended=)
GET /api/catalog/{id} Get full details for a single catalog entry
GET /api/catalog/categories List available categories
GET /api/catalog/stats Import statistics
POST /api/catalog/import Import ADMX zip (multipart/form-data)
POST /api/catalog/import-from-url Download and import ADMX templates from a URL
POST /api/catalog/import-local Import ADMX from a local server path

Policy Management

Method Endpoint Description
GET /api/policies List all PolicySets with versions
GET /api/policies/{id} Get a single PolicySet with its 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
POST /api/policies/{id}/add-setting Add a single setting to the current draft
GET /api/policies/{id}/draft-settings Get settings from the current draft version

Assignments

Method Endpoint Description
GET /api/assignments List all assignments
POST /api/assignments Create group assignment (priority + scope + optional push remediation)
PUT /api/assignments/{id}/priority Update assignment priority
PUT /api/assignments/{id}/push-remediation Enable/disable push remediation for assignment (optional immediate trigger)
POST /api/assignments/{id}/push-remediation/trigger Manually trigger push remediation command dispatch
DELETE /api/assignments/{id} Remove assignment
GET /api/groups/search Search Entra ID groups (for assignment UI)

Device Operations

Method Endpoint Description
GET /api/devices/{id}/effective-policy Resolve effective policy for device (supports ETag/304)
POST /api/devices/{id}/report Device reports compliance status (202 Accepted)
GET /api/devices/{id}/history Get device compliance history
POST /api/devices/{id}/logs Batch ingest device logs (detection/remediation)
GET /api/devices/{id}/logs Query device logs (filter by level, script type)

Monitoring

Method Endpoint Description
GET /api/monitoring/dashboard Compliance dashboard data
GET /api/monitoring/offline-devices Devices offline >24 hours
GET /api/monitoring/error-devices Devices with recent errors

Webhooks

Method Endpoint Description
POST /api/webhooks/group-change Microsoft Graph change notification receiver
GET /api/webhooks/changed-groups List groups with pending membership changes
POST /api/webhooks/acknowledge/{groupId} Acknowledge a group change (clear dirty flag)

Health

Method Endpoint Description
GET /health Health check

πŸ“Š How Policy Resolution Works

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" }

πŸ”¬ Root Cause Analysis

Why Intune Settings Catalog Fails for Chrome

  1. Intune ADMX ingestion writes to HKLM\SOFTWARE\Microsoft\PolicyManager\providers\{GUID}\...
  2. This relies on GP Client Service to mirror to HKLM\SOFTWARE\Policies\Google\Chrome
  3. On Entra ID–only devices, GP Client mirroring is broken (no RegisterGPNotification, no domain join)
  4. Chrome reads only from HKLM\SOFTWARE\Policies\Google\Chrome β€” policies never arrive

Why Direct Registry Write Works

  • Chrome's PolicyLoaderWin reads HKLM\SOFTWARE\Policies\Google\Chrome unconditionally
  • No domain-join check gates registry policy reading
  • Chrome polls registry every 15 minutes (kReloadInterval = base::Minutes(15))
  • Entra ID–only devices get FULLY_TRUSTED management authority β€” no policy filtering

Source Code Evidence (Chromium)

  • PolicyLoaderWin::InitOnBackgroundThread() β€” requires RegisterGPNotification() success
  • mdm_utils.cc::IsEnrolledToDomain() β€” GP registry path check fails on cloud-only devices
  • WinGPOListProvider β€” depends on Active Directory infrastructure not present on Entra-only devices

πŸ›‘οΈ Security

API Gateway (Azure API Management)

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:

  1. Device acquires OAuth2 token (client credentials + device certificate) from Entra ID
  2. APIM validates JWT (issuer, audience, expiry, required claims)
  3. APIM rate-limits per device identity (30 calls/hour/device)
  4. APIM strips any client-supplied identity headers (anti-spoofing)
  5. APIM sets trusted X-Forwarded-Device-Id from validated JWT claims
  6. APIM authenticates to backend using its managed identity (no shared secrets)
  7. Backend middleware verifies the request's appid claim 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)

Additional Security Controls

  • 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

πŸ“ˆ Scaling to 100k+ Devices

The solution is designed to handle large-scale enterprise environments (100,000+ devices) with minimal infrastructure cost. Three key optimizations make this possible:

1. ETag / 304 Not Modified

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.

2. Graph Change Notifications (Webhooks)

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-change receives notifications and marks affected groups
  • WebhookEndpoints.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.

3. Azure SQL S2 (50 DTU)

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

Scaling Summary

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)

Recommended SKUs for 100k+ Devices

The default Bicep deployment uses development-friendly SKUs. For production scale, upgrade to:

Component Default (Bicep) Recommended (100k+) Monthly Cost (est.)
App Service B1 S2 or P1v3 €70-140
Azure SQL Basic (5 DTU) S2 (50 DTU) €60-150
Service Bus Basic Standard €10
API Management Developer Consumption or Standard €50-300
App Configuration Free Standard €35
Total ~€225-635/month

πŸ“¦ Technology Stack

Component Technology
API .NET 10, Minimal API, Entity Framework Core 10
Admin UI Blazor Server, MudBlazor 8
Database Azure SQL (Entra-only auth), SQLite fallback for development
Auth Microsoft Identity Web 3.x, MSAL, Device Certificates
Group Resolution Microsoft Graph SDK 5.x + Change Notifications
Messaging Azure Service Bus (async device reports)
Config Azure App Configuration
Secrets Azure Key Vault
Observability Azure Monitor OpenTelemetry
Hosting Azure App Service (B1 default, scale to S2/P1v3)
API Gateway Azure API Management (Developer SKU default)
Client PowerShell 5.1 (Intune Proactive Remediation)
Policy Catalog Chrome ADMX/ADML parser (700+ policies)
Validation ChromePolicyValidator (schema validation for known policy types)

🀝 Contributing

  1. Fork the repo
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes
  4. Push to the branch
  5. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

🏷️ Keywords

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.

About

Chrome Policy Manager - Workaround for Chrome ADMX policies failing on Entra ID-only (Azure AD joined) devices. Server-side policy resolution with Intune Proactive Remediation delivery.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors