Google Workspace¶
Collects identity posture from Google Workspace — users, groups and membership, admin roles, user security posture, devices, inbound-SSO profiles, and Gemini-in-Workspace usage — via the Admin SDK and Cloud Identity APIs.
Auth model: a Google Cloud service account, in one of two setups:
- Domain-wide delegation (DWD) — the service account impersonates a Workspace super admin to read directory data. This is the recommended setup and the rest of this page assumes it.
- No delegation — the service account calls the Admin SDK directly, and must itself hold a Workspace admin role in the Admin console. See Alternative: no delegation.
Prerequisites¶
- A GCP service account with a JSON key (a dedicated one, or reuse your GCP collector service account).
- In that service account's GCP project, enable:
- Admin SDK API (
admin.googleapis.com) - Cloud Identity API (
cloudidentity.googleapis.com) — needed for inbound-SSO and Cloud Identity device surfaces.
- Admin SDK API (
- A real Workspace Super Admin account to impersonate
(e.g.
admin@example.com) — DWD setup only.
OAuth scopes¶
Only the user and group scopes are required. Each remaining scope unlocks one surface, and leaving it out degrades only that surface.
| Capability | Required | Scope |
|---|---|---|
| Directory users (read) | ✅ | https://www.googleapis.com/auth/admin.directory.user.readonly |
| Groups & membership | ✅ | https://www.googleapis.com/auth/admin.directory.group.readonly |
| Group members | ✅ | https://www.googleapis.com/auth/admin.directory.group.member.readonly |
| User security posture | — | https://www.googleapis.com/auth/admin.directory.user.security |
| Admin roles | — | https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly |
| Inbound SSO profiles | — | https://www.googleapis.com/auth/cloud-identity.inboundsso.readonly |
| Mobile devices | — | https://www.googleapis.com/auth/admin.directory.device.mobile.readonly |
| ChromeOS devices | — | https://www.googleapis.com/auth/admin.directory.device.chromeos.readonly |
| Cloud Identity devices | — | https://www.googleapis.com/auth/cloud-identity.devices.readonly |
| Gemini-in-Workspace usage | — | https://www.googleapis.com/auth/admin.reports.audit.readonly |
Copy-paste block for the DWD scopes field (one comma-separated line):
https://www.googleapis.com/auth/admin.directory.user.readonly,https://www.googleapis.com/auth/admin.directory.group.readonly,https://www.googleapis.com/auth/admin.directory.group.member.readonly,https://www.googleapis.com/auth/admin.directory.user.security,https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly,https://www.googleapis.com/auth/cloud-identity.inboundsso.readonly,https://www.googleapis.com/auth/admin.directory.device.mobile.readonly,https://www.googleapis.com/auth/admin.directory.device.chromeos.readonly,https://www.googleapis.com/auth/cloud-identity.devices.readonly,https://www.googleapis.com/auth/admin.reports.audit.readonly
What the Gemini scope collects
admin.reports.audit.readonly reads the Admin SDK Reports audit stream
to land Gemini-in-Workspace usage as an application with per-user access
edges — only the set of users seen in the trailing window, never any
prompt or response content. Skipping it costs only that surface: it degrades
to unobserved and previously collected rows are kept, never deleted.
Register domain-wide delegation¶
In the Google Admin console → Security → Access and data control → API controls → Manage Domain-Wide Delegation → Add new:
- Client ID = the service account's numeric OAuth client ID (from the
service-account JSON key's
client_id, or GCP Console → IAM & Admin → Service Accounts → the service account → Unique ID). - OAuth scopes = the full comma-separated list above.
Register the entire scope list, exactly
Two Google behaviors bite here:
- All-or-nothing token mint: when LimaCharlie requests a token, if
any requested scope is not authorized for the client, Google rejects
the whole request. Registering only
admin.directory.user.readonlywill still fail because other surfaces request additional scopes. - Literal string match: the broader
…/admin.directory.userdoes not satisfy the narrower…/admin.directory.user.readonly. No typos or trailing spaces. - DWD changes can take ~10 minutes (occasionally up to 24h) to propagate.
Create the credentials secret¶
The Workspace provider record has no field for the impersonation admin — it lives inside the secret, which is a wrapper around the service-account key:
{
"service_account_json": { "type": "service_account", "project_id": "...", "private_key": "...", "client_email": "...", "...": "..." },
"admin_email": "admin@example.com"
}
service_account_json— the entire raw Google service-account key JSON, nested as this key's value.admin_email— the Super Admin to impersonate. Omit it only for the no-delegation setup below.domain— optional, and a filter rather than a label. Most connections should leave it out entirely.
domain narrows what is collected
Setting domain enumerates that one domain instead of the whole
customer. On a tenant with secondary or additional domains, every user and
group outside it is silently absent from the inventory — the sweep succeeds
and the connection looks healthy. Leave domain out unless you
deliberately want a single-domain inventory.
To declare which domains count as internal (for external-collaborator
detection), use the provider record's internal_domains instead. That is a
classification list and does not restrict collection.
Do not flatten the wrapper
Do not place admin_email / domain at the top level next to the
service-account-key fields. A secret that looks like a bare service-account
key is read as the no-delegation form: the impersonation admin is
ignored, the token is minted with no subject, and Google returns
HTTP 400: Invalid Input on the first directory call — unless the
service account itself holds a Workspace admin role.
Store it:
secret set wraps the value into the secret record's {"secret": "..."}
envelope for you.
Or in the web app: Organization Settings → Secrets Manager → Add, name it
gw-credentials, and paste the JSON.
Alternative: no delegation¶
If you would rather not register domain-wide delegation, the raw
service-account key JSON is accepted as the secret on its own — the file GCP
hands you, stored verbatim with no wrapper and no admin_email:
In this form the service account calls the Admin SDK as itself instead of
impersonating an admin, so it must hold a Workspace admin role granted in
the Google Admin console — assign the service account a read-only or custom
admin role covering the directory surfaces you want. No DWD registration is
needed, and there is no way to set domain: the whole customer is enumerated.
Everything else on this page is unchanged; provider test reports the same
checks, and a surface the role does not cover fails exactly as an unregistered
scope would.
Create the provider record¶
provider.yaml:
provider_type: google_workspace
workspace_customer_id: my_customer # or an explicit customer ID
credentials: hive://secret/gw-credentials
internal_domains: [example.com]
In the web app: Add provider → Google Workspace, then set Customer ID
(my_customer), Credentials (gw-credentials), and Refresh interval.
Use my_customer unless you have a reason not to
my_customer is an alias Google resolves to the customer the impersonated
admin (or the service account) belongs to, so it is correct for every tenant
and there is nothing to look up or mistype. Set an explicit ID only when a
reseller-style account has to be pinned to a specific customer.
Customer ID is not your organization's name. It is an opaque ID Google
issues, like C01ab2cd3. Find it in the Google Admin console →
Account → Account settings → Profile, next to Customer ID.
Any other value is rejected — but Google rejects it in a way that reads like
a permission problem, not a typo: the Admin SDK answers an unrecognized
customer with a bare HTTP 400: Bad Request that never names the field or
echoes the value. Every required surface fails at once, so the connection
shows Failed and provider test's first check — Authenticate + read
directory users — fails, even though the credential and the delegation are
fine. If provider test reports a 400 on that check, it re-runs the same
read against my_customer and tells you outright when the customer ID is
the culprit.
Verify¶
| Check | Required | Meaning if it fails |
|---|---|---|
core |
✅ | Authentication plus the directory user read. A failure here means the delegation (or, in the no-delegation setup, the service account's admin role) is wrong — nothing else can be probed meaningfully. |
groups |
✅ | Groups and membership unavailable — the group edges that complete the GCP IAM picture are missing. |
security |
— | Per-user security posture (2SV enrolment/enforcement) unavailable. |
roles |
— | Admin-role assignments unavailable. |
sso |
— | Inbound-SSO profile posture unavailable. |
devices_mobile |
— | Mobile device inventory unavailable. |
devices_chromeos |
— | ChromeOS device inventory unavailable. |
devices_ci |
— | Cloud Identity device inventory unavailable. |
reports |
— | Gemini-in-Workspace usage unavailable (no AI-application rows or per-user access edges). |
An ungranted optional scope still shows the connection as Failed
The optional surfaces above degrade safely — the surface goes unobserved,
its previously collected rows are kept rather than deleted, and
provider test still reports OK overall, because only the required
checks decide that verdict.
The Last Sync badge on the providers page works differently. It reports partial coverage: any collector left uncovered marks the sync Failed, even when every required surface succeeded. So a connection that is working as intended, minus one optional scope you chose not to grant, sits on a permanent Failed badge.
Hover the badge for the error detail — it names the collectors that were left uncovered and why. If the reason is a scope you deliberately skipped, the connection is healthy and the badge can be ignored; grant the scope to clear it.
Troubleshooting¶
provider test error |
Cause | Fix |
|---|---|---|
HTTP 400: Bad Request on the user check, with every other check failing too |
workspace_customer_id is not a customer ID Google recognizes — most often the organization's name rather than the ID. Google returns this same opaque 400 for every customer-aimed call, which reads as a permission problem and sends people back to re-audit delegation and scopes that are already correct |
Set workspace_customer_id to my_customer, or to the ID from Admin console → Account → Account settings → Profile. provider test names this explicitly when it can confirm it: the same read is retried against my_customer, and if that succeeds the check reports the real customer ID to use |
HTTP 400: Invalid input on the user check |
Secret is missing or mis-nested the impersonation admin → token has no subject → my_customer unresolvable |
Use the wrapper envelope above: nest the key under service_account_json, with admin_email as a sibling. If you meant the no-delegation setup, grant the service account a Workspace admin role instead |
| Users or groups from a secondary domain are missing | domain is set in the secret, narrowing collection to that one domain |
Remove domain from the secret; declare internal domains with internal_domains on the record |
token mint failed (HTTP 401): scope not granted to the delegated admin |
DWD missing scopes (all-or-nothing mint), a non-.readonly variant, or not yet propagated |
Register the full scope list exactly; wait for propagation; confirm the service account's client ID matches |
core fails: HTTP 403: Not Authorized to access this resource/api |
A token was minted (so the scopes are registered) but the caller has no Workspace admin authority. Almost always the secret is a raw service-account key with no admin_email — typically the GCP provider's secret reused verbatim — so nothing is impersonated and the delegation you configured is never used. It is silently accepted as the no-delegation form. Otherwise, admin_email names a user who is not a Super Admin |
Give Workspace its own secret in the wrapper envelope with admin_email; leave the GCP provider's secret untouched. Reusing the same service account is fine — reusing the same secret is not |
HTTP 403: … API has not been used in project … |
Admin SDK / Cloud Identity API not enabled | Enable the named API in the service account's project |
reports fails: HTTP 401: Access denied. You are not authorized to read activity records. |
The token minted (so the DWD registration itself is fine) but the impersonated admin cannot read the Reports audit stream — either admin.reports.audit.readonly is missing from the DWD scope list, or admin_email names an admin without the Reports privilege |
Add the scope to the delegation and impersonate a Super Admin. Optional surface: leaving it as-is drops only Gemini-in-Workspace usage — but it does leave the connection's Last Sync badge on Failed, per the note above |