Skip to content

robgrame/ChromePolicyManager

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

107 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Chrome Policy Manager

Server-side Chrome policy delivery for Entra ID–only (Azure AD joined) devices β€” delivering up-to-date Chrome policies without the Intune Settings Catalog lag and the limitations of ADMX import.

.NET 10 Azure Intune License: MIT

🎯 The Problem

Managing Chrome policies through Intune is slow and constrained:

  1. Settings Catalog lag β€” Chrome settings exposed via the Intune Settings Catalog typically trail the latest Chrome release by ~2 months, so newly added policies aren't available when you need them.
  2. ADMX import limits β€” Importing Chrome's ADMX as a custom template is the workaround, but it has hard limitations:
    • Max 20 ADMX files can be imported per tenant
    • Policy duplication β€” imported policies are duplicated in the update catalog, cluttering the experience
    • No clean removal β€” an old ADMX cannot be deleted while it is still referenced by a custom template policy
  3. Operational friction β€” keeping templates current means re-importing and reconciling assignments on every Chrome version bump.

πŸ’‘ 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, decoupling policy delivery from the Settings Catalog and ADMX import constraints.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                             ARCHITECTURE                               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                        β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   SignalR    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   APIM    β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚   β”‚  Admin UI     │◀───realtime──│   REST API    │◀──mTLS───▢│ Intuneβ”‚ β”‚
β”‚   β”‚  (Blazor      │── REST ─────▢│  (.NET 10)    β”‚           β”‚ Remed.β”‚ β”‚
β”‚   β”‚   Server)     β”‚              β”‚  Min. API     β”‚           β”‚ (PS1) β”‚ β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β””β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚        β–²  PolicyStatusHub           β”‚   β”‚   β”‚                          β”‚
β”‚        β”‚  CommandStatusHub          β”‚   β”‚   └────────────┐             β”‚
β”‚        β”‚                       β”Œβ”€β”€β”€β”€β”΄β”€β”€β”β”‚           β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚   β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”               β”‚ SQL DB β”‚β”‚           β”‚  MS Graph  β”‚     β”‚
β”‚   β”‚ Event Grid  │◀─publish(MI)──  (PE)  β”‚β”‚           β”‚  (delta +  β”‚     β”‚
β”‚   β”‚ topic       β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚           β”‚  webhooks) β”‚     β”‚
β”‚   β”‚ policy-status│──webhookβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β”‚                                    β”‚
β”‚                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”‚
β”‚                          β”‚   Service Bus       β”‚  cpm-commands          β”‚
β”‚                          β”‚   (MI only,         β”‚  cpm-command-status    β”‚
β”‚                          β”‚    disableLocalAuth)β”‚  device-reports        β”‚
β”‚                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β”‚                                    β”‚ trigger                            β”‚
β”‚                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”‚
β”‚                          β”‚  Worker             β”‚  privileged Graph      β”‚
β”‚                          β”‚  (Azure Functions)  β”‚  actions (push remed., β”‚
β”‚                          β”‚                     β”‚  membership sync)      β”‚
β”‚                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β”‚                                                                        β”‚
β”‚   Cross-cutting:  Key Vault Β· App Configuration Β· App Insights         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

✨ Key Features

Feature Description
ADMX Catalog Ingestion Parse Chrome ADMX/ADML templates β†’ browse 700+ policies with descriptions, types, categories
Multi-Version ADMX Registry Multiple Chrome ADMX template versions coexist with lifecycle states (Staged β†’ Active β†’ Retired); pick which ADMX version each PolicySet version is authored against, with no 20-template cap and clean version deletion
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
Machine & User Targeting Apply policies to the device (HKLM) or per logged-on user (HKCU) β€” see the User-Level Policies Admin Guide
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

πŸ“Έ Screenshots

Dashboard

Compliance overview at a glance β€” total devices, compliant/error/offline counters, and the most recent device reports with policy hash, Chrome version and last contact.

Dashboard

Policy Catalog

Browse the ingested Chrome ADMX catalog (722 policies) with search, category/scope filters, type badges and an "update available" banner when Google ships a newer template. The ADMX template registry lists every imported version with its status (Staged / Active / Retired) and policy counts β€” activate, retire or delete versions inline, and import a new version without losing the previous ones.

Policy Catalog

Add Policy to a PolicySet

Per-policy editor with inline description, value guidance, target PolicySet selection and a registry write preview before adding.

Add Policy to PolicySet

PolicySet Versions

Immutable, hash-tracked versions per PolicySet β€” inspect the resolved Chrome policy keys (JSON) for any version, and choose which Chrome ADMX template version each one is authored against (defaults to the Active template, snapshotted on the version).

PolicySet Versions

Assign to Entra ID Group

Assign a policy version to an Entra ID security group with priority-based conflict resolution, Mandatory/Recommended scope, and optional Windows-style push remediation.

Assign Policy to Group

Policy Assignments

All assignments with group, scope, priority, target policy version, enabled state and push-remediation toggle.

Policy Assignments

Device Monitoring

Per-device observability β€” compliance status, policy hash, Chrome/OS versions, hardware model, script version, keys written and last contact.

Device Monitoring

Client Certificate Trust Configuration

Manage the trusted CA chain (Root/Subordinate) used to validate device client certificates on /api/devices/*, persisted to Azure App Configuration and consumed by the API.

Client Certificate Configuration

πŸ—οΈ 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, Command, EventGrid, Config
β”‚   β”‚   β”‚   β”œβ”€β”€ Hubs/                        # SignalR: PolicyStatusHub, CommandStatusHub (realtime portal)
β”‚   β”‚   β”‚   β”œβ”€β”€ Middleware/                 # APIM Gateway authentication middleware
β”‚   β”‚   β”‚   β”œβ”€β”€ Models/                     # PolicySet, Version, Assignment, CatalogEntry, DeviceLog
β”‚   β”‚   β”‚   └── Services/                   # AdmxParser, EffectivePolicy, Graph, Reporting, Validator,
β”‚   β”‚   β”‚                                   #   CommandPublisher, EventGridEventPublisher, PushRemediation
β”‚   β”‚   β”œβ”€β”€ ChromePolicyManager.Admin/      # Blazor Server Admin UI (MudBlazor)
β”‚   β”‚   β”‚   └── Components/Pages/           # Dashboard, Catalog, Policies, Assignments, Devices
β”‚   β”‚   └── ChromePolicyManager.Worker/     # Azure Functions worker (Service Bus-triggered)
β”‚   β”‚       β”œβ”€β”€ Functions/                  # CommandHandlerFunction (cpm-commands trigger)
β”‚   β”‚       └── Services/                   # PrivilegedGraphActions, StatusPublisher
β”‚   β”œβ”€β”€ Shared/
β”‚   β”‚   └── ChromePolicyManager.Contracts/  # Shared contracts: PrivilegedCommand, CommandStatus,
β”‚   β”‚                                       #   PolicyStatusEvents, QueueNames
β”‚   └── Client/
β”‚       β”œβ”€β”€ Detect-ChromePolicy.ps1         # Intune detection script (supports inline remediation)
β”‚       └── Remediate-ChromePolicy.ps1      # Intune remediation script
β”œβ”€β”€ infra/
β”‚   β”œβ”€β”€ Deploy.ps1                          # Unified deploy: infra (Bicep) + code (API + Admin)
β”‚   β”œβ”€β”€ Deploy-Infrastructure.ps1           # Imperative az-CLI provisioning (alternative)
β”‚   β”œβ”€β”€ main.bicep                          # Infrastructure-as-Code (Bicep), dev/prod SKU tiers
β”‚   β”œβ”€β”€ main.dev.bicepparam                 # Dev parameters (cost-optimized SKUs)
β”‚   β”œβ”€β”€ main.prod.bicepparam                # Prod parameters (production-grade SKUs)
β”‚   └── 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 + Code

The unified Deploy.ps1 script provisions the Azure infrastructure (Bicep) and builds/deploys the API + Admin code in one pass. SKUs are sized automatically per environment.

cd infra

# Dev (cost-optimized SKUs)
.\Deploy.ps1 -EnvironmentName dev -ClientId <api-app-id> -ClientSecret (Read-Host -AsSecureString)

# Prod (production-grade SKUs, Service Bus enabled)
.\Deploy.ps1 -EnvironmentName prod -ClientId <api-app-id> -ClientSecret (Read-Host -AsSecureString)

# Preview infra changes without applying (Bicep what-if)
.\Deploy.ps1 -EnvironmentName prod -SkipCode -WhatIf

Useful switches: -SkipInfra (deploy code only), -SkipCode (deploy infra only), -SubscriptionId, -Location.

This creates: Resource Group, VNet (App Service VNet integration), SQL Server (Private Endpoint, Entra-only auth), App Service Plan + Web Apps (API + Admin), Key Vault, App Configuration, API Management, Application Insights, and (prod) Service Bus with Private Endpoint.

SKU tiers (dev vs prod)

Resource dev prod
App Service Plan B1 (Basic) P1v3 Γ—2 (PremiumV3)
SQL Database Basic (5 DTU) S2 (Standard, 50 DTU)
API Management off (direct API) Standard
App Configuration Free Standard
Service Bus off by default Standard (Private Endpoint)
Log Analytics retention 30 days 90 days

The tier is selected by the skuTier Bicep parameter (defaults from environmentName) and the matching parameter files: main.dev.bicepparam / main.prod.bicepparam.

APIM in dev: APIM is the most expensive fixed-cost resource (~€45/month). It is off by default in dev (deployApim=false). Devices then call the backend directly β€” set CPM_USE_DIRECT_API=true on the device and leave ApimGateway:ClientId unset so the backend middleware allows direct access. This skips mTLS, rate limiting and edge JWT validation, so device endpoints are unauthenticated in dev β€” acceptable for development, not for prod.

Note: Deploy-Infrastructure.ps1 (imperative az-CLI variant) remains available for step-by-step provisioning without Bicep.

2. Import Chrome Policy Catalog

Download the Chrome ADMX templates and upload via the Admin UI or API:

# Via API (multipart upload). Add -F "activate=true" to make this the Active
# template immediately; otherwise it is imported as Staged and coexists with
# the current Active version until you activate it from the registry.
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=&version=; version selects which ADMX template, defaults to Active)
GET /api/catalog/{id} Get full details for a single catalog entry
GET /api/catalog/categories List available categories (?version= to target a specific ADMX template)
GET /api/catalog/stats Import statistics
GET /api/catalog/templates List imported ADMX template versions (status, counts, imported date)
POST /api/catalog/templates/{id}/activate Make a version the Active template (demotes the previous Active to Retired)
POST /api/catalog/templates/{id}/retire Retire a non-active version
DELETE /api/catalog/templates/{id} Delete a version (blocked while Active or referenced by a Draft policy version)
POST /api/catalog/import Import ADMX zip (multipart/form-data; activate to set Active after import)
POST /api/catalog/import-from-url Download and import ADMX templates from a URL (?activate=)
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" }

πŸ”¬ How Direct Registry Delivery Works

Why bypass the Settings Catalog / ADMX import

Instead of relying on Intune's ADMX ingestion pipeline β€” with the Settings Catalog lag and ADMX import limits described above β€” Chrome Policy Manager writes Chrome policies directly to the registry keys Chrome already reads. New policies are available the moment the catalog is imported into CPM, independent of the Intune release cadence.

Why Direct Registry Write Works

  • Chrome's PolicyLoaderWin reads HKLM\SOFTWARE\Policies\Google\Chrome (and the HKCU equivalent) unconditionally
  • Registry-based policy loading does not require Active Directory, a domain join, or Group Policy
  • Chrome polls the 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 loads machine policies from HKLM\SOFTWARE\Policies\Google\Chrome and user policies from the HKCU hive
  • The registry policy provider is always active on Windows, regardless of join state β€” so writing the keys directly is sufficient for Chrome to apply them

πŸ›‘οΈ 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.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    mTLS client cert   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   Managed Identity   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Device     β”‚ ───────────────────▢ β”‚    APIM      β”‚ ────────────────────▢ β”‚  Backend API β”‚
β”‚ (PowerShell) β”‚   (issued by the     β”‚   Gateway    β”‚                       β”‚  (.NET 10)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    customer PKI)      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                             β”‚
                                       β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
                                       β”‚ Validates   β”‚
                                       β”‚ client cert β”‚
                                       β”‚ vs trusted  β”‚
                                       β”‚ CA chain    β”‚
                                       β”‚ Rate limits β”‚
                                       β”‚ Strips      β”‚
                                       β”‚ spoofable   β”‚
                                       β”‚ headers     β”‚
                                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Security flow:

  1. Device presents a client certificate issued by the customer's PKI β€” deployed to the device via Intune (SCEP/PKCS) β€” over mTLS
  2. APIM validates the client certificate against the trusted CA chain configured in CPM (Root + Subordinate CAs)
  3. APIM rate-limits per device identity (30 calls/hour/device)
  4. APIM strips any client-supplied identity headers (anti-spoofing)
  5. APIM sets a trusted X-Forwarded-Device-Id derived from the validated certificate
  6. APIM authenticates to the backend using its managed identity (no shared secrets)
  7. Backend middleware verifies the request originates from APIM and re-validates the client certificate against the configured CA chain

Separation of concerns:

Layer Responsibility
APIM Device mTLS (client certificate), 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, mTLS client certificates (customer PKI)
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