2024 · production

Microsoft Intune Connector

Multi-tenant device ingestion pipeline integrating Microsoft Intune data via the Graph API.

PythonMicrosoft Graph APIAzure ADKubernetesPostgreSQLAzure Key Vault

Problem

Enterprise customers needed their device inventory from Microsoft Intune available in a unified asset management platform. The platform was multi-tenant, serving hundreds of customer organizations, each with separate Azure AD tenants and different Intune configurations.

The challenge was OAuth credential lifecycle management at scale, Graph API rate limits that varied per tenant tier, and the need to handle partial failures without affecting other tenants.

Architecture

Authentication — Each tenant registers an Azure AD App Registration. Credentials (client ID + secret) are stored encrypted in a secrets vault. A credential manager handles token refresh, expiry detection, and rotation alerts.

Ingestion — Per-tenant ingestion jobs are dispatched as Kubernetes Jobs on a configurable schedule (typically hourly). Jobs use the Graph API's delta query capability to fetch only changes since the last run, reducing API calls by ~80% compared to full syncs.

Rate Limiting — The Graph API enforces per-tenant rate limits that vary by license tier. The connector implements exponential backoff with jitter and a per-tenant token bucket to stay within limits. Tenants that hit limits consistently are flagged for review.

State Management — Delta tokens and ingestion metadata are stored per-tenant in Postgres. This allows resuming from the last successful delta rather than re-ingesting the full dataset after a failure.

Tradeoffs

Delta sync vs full sync — Delta queries are efficient but occasionally get invalidated by Microsoft (e.g., when the tenant's data model changes). Falling back to full sync is expensive. We mitigated this by running a weekly full sync as a reconciliation pass.

Per-tenant jobs vs batching — Running one Kubernetes Job per tenant is operationally simple but doesn't scale linearly in terms of cluster resources. At hundreds of tenants, batching multiple tenants per job would be more efficient. We accepted the current approach for isolation guarantees.

Lessons Learned

Azure Graph API behavior is inconsistent across tenant tiers. Free-tier tenants have much more aggressive rate limits than E3/E5 tenants, and the error responses aren't always clearly documented. Defensive handling and comprehensive logging are essential.

Multi-tenancy at the infrastructure level (separate jobs, separate credentials, separate state) is harder to build but far easier to debug and operate than shared-state designs.

← all projects