Connectors are off by default. A connector becomes available only when both its client ID and client secret are present in the deployment. No connector code path runs until then.
How it fits together
| Piece | Who provides it | Where it goes |
|---|---|---|
| Entra app registration (client ID, secret, tenant) | You, in the Azure portal | Deployment secrets (below) |
| OAuth callback base URL | Your deployment’s public API origin | NEBULA_CONNECTOR_API_BASE_URL / connectors.apiBaseUrl |
| Token-signing HMAC | Auto-generated (Compose) or you (K8s) | NEBULA_CONNECTOR_TOKEN_KEY |
| End-user account authorization | Each of your users | Connectors API / app UI |
NEBULA_CONNECTOR_TOKEN_KEY) signs every connector’s OAuth state and stored tokens. It is required for any connector and should be treated like NEBULA_SECRET_KEY — long-lived, rotating it invalidates all in-flight authorizations.
Step 1 — Register the Entra app
Create the registration
In the Azure portal: Entra ID → App registrations → New registration. Register a single-tenant app (or multi-tenant if you serve external organizations). Copy the Application (client) ID and the Directory (tenant) ID.
Create a client secret
Certificates & secrets → New client secret. Copy the secret Value (not the secret ID — the value is shown only once).
Add the redirect URIs
Authentication → Add a platform → Web, then add one redirect URI per connector, substituting your deployment’s public API origin:The origin must match
NEBULA_CONNECTOR_API_BASE_URL (Compose) / connectors.apiBaseUrl (Helm) exactly. Entra rejects non-HTTPS and non-public redirect URIs (except localhost).Grant Graph permissions
API permissions → Microsoft Graph → Delegated permissions, add the scopes for the connectors you intend to offer, then Grant admin consent:
| Connector | Delegated scopes |
|---|---|
| Outlook | Mail.Read |
| OneDrive | Files.Read |
| SharePoint | Sites.Read.All, Files.Read.All |
| Teams | Team.ReadBasic.All, Channel.ReadBasic.All, ChannelMessage.Read.All, Chat.Read, ChatMessage.Read |
| All | User.Read, offline_access |
Step 2 — Wire the credentials into your deployment
secrets.backend: eso-aws or eso-vault, put NEBULA_CONNECTOR_TOKEN_KEY, NEBULA_M365_CLIENT_ID, and NEBULA_M365_CLIENT_SECRET in your secret store under the path the ExternalSecret syncs. The chart envFrom-mounts the synced Secret onto all first-party Nebula workloads (api, worker, graph-engine, compactor, and the migration Job), so every key lands on every Nebula pod. Only the api (OAuth connect) and worker (sync) actually read the connector keys; the other workloads ignore them. Keep this synced path scoped to Nebula’s own secrets — don’t co-locate unrelated application secrets under it.
Apply the change (bootstrap.sh re-run for Compose, helm upgrade for Kubernetes) and the connectors are live. Both the API pod (OAuth connect flow) and the worker pod (background sync + webhooks) read the same configuration.
Step 3 — Connect accounts
Connector setup is per-user, not per-deployment. Each user authorizes their own Microsoft account through the Connectors API or the app’s connector settings. The two-phase providers (OneDrive, SharePoint, Teams) prompt for a folder / library / channel selection after authorization; Outlook syncs immediately.Other connectors
Gmail / Google Drive, Notion, and Slack follow the same base shape — register an OAuth app with that provider’s console, add the matching redirect URI (<base>/v1/connectors/<provider>/callback), and set the provider’s NEBULA_<PROVIDER>_CLIENT_ID / _SECRET pair (Compose) or the equivalent secrets.values keys (Helm). Two need provider-specific extras beyond that pair:
- Slack also requires a signing secret (
NEBULA_SLACK_SIGNING_SECRET) and an Events API subscription to verify and receive inbound events. - Gmail real-time sync requires Google Pub/Sub push: a topic and a verified push service account (
NEBULA_GMAIL_PUBSUB_TOPIC,NEBULA_GMAIL_PUBSUB_SERVICE_ACCOUNT,NEBULA_GMAIL_PUBSUB_AUDIENCE). Without them the connector still authorizes, but live mail updates are disabled.
.env.enterprise.example. For Helm, only the M365 keys are pre-seeded in values.yaml secrets.values; add each other provider’s keys there the same way — any key under secrets.values becomes an env var on the pods.