← Google Workspace Security Audit

Setup guide

Everything you must provision before this skill can run β€” the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.

Some setupβ‰ˆ 25-40 mingoogle-workspace

Create a Google Cloud service account with a JSON key, then have a Workspace super admin authorise its numeric Client ID for a fixed list of read-only Admin SDK, Drive and Alert Center scopes via domain-wide delegation.

You need to already have

  • Super Admin on the Google Workspace account (only a super admin can authorise domain-wide delegation)
  • Owner or Editor on a Google Cloud project in the same organisation, with billing not required but the Admin SDK API enabled
  • Google Workspace Business Standard or above if Drive audit events are in scope (Business Starter has no Drive audit log)
  • Local Python 3.9+ with google-auth and requests installed

How the pieces connect

Create GCP projectconsole.cloud.google.comEnable the APIsAdmin SDK, Reports…Service account+ JSON keyCopy client IDnumeric, not emailAdmin console: authorise the client IDSecurity > API controls > Domain-wide delegationImpersonate an adminsubject = admin@yourdomainThe delegation step happens in the Workspace Admin console, not in Google Cloud β€” this is the step most setups miss.Scopes entered there must match the scopes your code requests exactly, or you get "unauthorized_client".A service account cannot read a user's data without impersonating a real admin via the subject parameter.

Credentials this skill needs

Set these as environment variables. Never paste secrets into a chat or commit them.

VariableWhat it isWhere to get it
GOOGLE_WORKSPACE_SA_KEY_JSON
secret
Service account JSON key (base64-encoded)
format: base64 of the downloaded service-account JSON key file
Google Cloud console > IAM & Admin > Service Accounts > click the service account > Keys tab > Add key > Create new key > JSON > Create, then base64-encode the downloaded file (base64 -w0 key.json on Linux, [Convert]::ToBase64String([IO.File]::ReadAllBytes('key.json')) in PowerShell)
GOOGLE_WORKSPACE_ADMIN_SUBJECT
Super admin email to impersonate
format: email address, e.g. [email protected]
Admin console > Directory > Users - pick a live, licensed account that holds the Super Admin role (Admin console > Account > Admin roles > Super Admin > Admins)
GOOGLE_WORKSPACE_CUSTOMER_ID
optional
Workspace customer ID
format: the literal string my_customer, or a C-prefixed ID such as C01abc23d
Admin console > Account > Account settings > Profile > Customer ID. Leave unset to use the alias my_customer, which resolves to the impersonated admin's own account

Permissions to grant

https://www.googleapis.com/auth/admin.directory.user.readonlyAPI scopeadmin consent required

Page every user to read isAdmin, isDelegatedAdmin, isEnrolledIn2Sv, isEnforcedIn2Sv, suspended, lastLoginTime and orgUnitPath.

↳ This is already the read-only variant; do not authorise admin.directory.user, which permits creating and deleting users.

https://www.googleapis.com/auth/admin.directory.user.securityAPI scopeadmin consent required

Call users.tokens.list to enumerate the third-party OAuth clients each user has actually granted, and the scopes they hold.

↳ No read-only variant exists - this scope also permits revoking tokens and application-specific passwords. If the tenant refuses it, drop it and derive app grants from the token audit log instead, at the cost of missing grants older than the lookback window.

https://www.googleapis.com/auth/admin.directory.rolemanagement.readonlyAPI scopeadmin consent required

List admin roles and role assignments so privilege can be joined to identities.

↳ Already read-only; admin.directory.rolemanagement would allow creating and assigning roles.

https://www.googleapis.com/auth/admin.directory.domain.readonlyAPI scopeadmin consent required

List verified domains so the audit can tell internal sharing from external sharing.

https://www.googleapis.com/auth/admin.directory.orgunit.readonlyAPI scopeadmin consent required

Resolve org unit paths and IDs when the audit is scoped to a sub-tree rather than the whole customer.

↳ Optional - omit if you always audit the entire customer.

https://www.googleapis.com/auth/admin.reports.audit.readonlyAPI scopeadmin consent required

Read the login, token, drive and admin audit logs via Reports activities.list.

↳ This is the only audit-log scope; it cannot be narrowed to a single application.

https://www.googleapis.com/auth/admin.reports.usage.readonlyAPI scopeadmin consent required

Pull the per-user usage report (accounts:is_2sv_enrolled, accounts:is_super_admin) to cross-check Directory counts.

↳ Optional - skip if you trust the Directory enumeration and do not need the reconciliation step.

https://www.googleapis.com/auth/drive.readonlyAPI scopeadmin consent required

With useDomainAdminAccess=true, list shared drives and their permissions to find files exposed to anyone or to an external domain.

↳ Broad and restricted by Google - it grants read access to every file in the tenant. Only authorise it when the operator asks for files-depth Drive analysis; the audit-log-only pass needs just admin.reports.audit.readonly.

https://www.googleapis.com/auth/apps.alertsAPI scopeadmin consent required

List Alert Center alerts (government-backed attack warnings, suspicious login alerts, leaked password alerts) to corroborate audit-log findings.

↳ Optional - omit if the tenant does not use Alert Center.

Step by step

  1. 1

    Create or select a Google Cloud project

    In the Google Cloud console, click the project picker in the top bar and choose New Project, or reuse an existing project owned by the same organisation. Note the project ID; the service account will live here.

    Open in the console / docs β†—
  2. 2

    Enable the Admin SDK API (and Drive / Alert Center if needed)

    Go to APIs & Services > Library, search for 'Admin SDK API' and click Enable. Repeat for 'Google Drive API' if you want files-depth sharing analysis, and 'Google Workspace Alert Center API' if you want alerts. Enabling can take a minute to propagate.

    Open in the console / docs β†—
  3. 3

    Create the service account

    IAM & Admin > Service Accounts > Create service account. Give it a name such as 'workspace-security-audit', click Create and continue, then click Done. Do NOT grant it any Google Cloud IAM roles - it needs none; its authority comes entirely from Workspace domain-wide delegation.

    Open in the console / docs β†—
  4. 4

    Create and download a JSON key

    Click the new service account, open the Keys tab, then Add key > Create new key > JSON > Create. The file downloads once and cannot be re-downloaded. Base64-encode it into GOOGLE_WORKSPACE_SA_KEY_JSON and store it in a secret manager, never in the repo.

    Open in the console / docs β†—
  5. 5

    Copy the service account's numeric Client ID

    On the service account's Details tab, copy the 21-digit value labelled 'Unique ID' (also shown as OAuth 2 Client ID). Domain-wide delegation is keyed on this number, not on the service account email.

    Open in the console / docs β†—
  6. 6

    Authorise domain-wide delegation in the Admin console

    Sign in to the Admin console as a super admin. Go to Security > Access and data control > API controls > Manage Domain Wide Delegation > Add new. Paste the numeric Client ID, then paste the scope list as a single comma-separated line with no spaces, and click Authorize. Scope matching is exact - a broader scope does not cover a narrower request.

    Open in the console / docs β†—
  7. 7

    Confirm the impersonation subject really is a super admin

    Admin console > Account > Admin roles > Super Admin > Admins. The address you put in GOOGLE_WORKSPACE_ADMIN_SUBJECT must appear there and must be an active, licensed, non-suspended account. Service accounts and non-admin mailboxes will fail with 403.

    Open in the console / docs β†—
  8. 8

    Export the environment variables and verify

    Export GOOGLE_WORKSPACE_SA_KEY_JSON and GOOGLE_WORKSPACE_ADMIN_SUBJECT (plus GOOGLE_WORKSPACE_CUSTOMER_ID if not using my_customer), install google-auth and requests, then run the verify command below. Delegation changes usually apply within minutes but Google documents up to 24 hours.

    Open in the console / docs β†—

Check it worked

python -c "import base64,json,os,google.auth.transport.requests as g; from google.oauth2 import service_account as sa; c=sa.Credentials.from_service_account_info(json.loads(base64.b64decode(os.environ['GOOGLE_WORKSPACE_SA_KEY_JSON'])), scopes=['https://www.googleapis.com/auth/admin.directory.user.readonly']).with_subject(os.environ['GOOGLE_WORKSPACE_ADMIN_SUBJECT']); r=g.AuthorizedSession(c).get('https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer&maxResults=1'); print(r.status_code, r.text[:300])"

If you hit an error

unauthorized_client: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.

Cause: The service account's numeric Client ID is not registered under Domain-wide delegation, or the scope string you requested is not byte-for-byte present in the authorised list. Google matches scopes exactly - authorising admin.directory.user does not cover a request for admin.directory.user.readonly.

Fix: Admin console > Security > Access and data control > API controls > Manage Domain Wide Delegation. Confirm the Client ID matches the service account's Unique ID, then edit the entry so its scope list contains every scope your code requests, character for character. Wait a few minutes and retry.

403 Not Authorized to access this resource/api

Cause: The delegation is configured but GOOGLE_WORKSPACE_ADMIN_SUBJECT is not a super admin, is suspended, is unlicensed, or does not hold the privilege behind the endpoint you called (for example roleAssignments requires role management privileges).

Fix: Point the subject at an active super admin listed under Admin console > Account > Admin roles > Super Admin > Admins. Do not impersonate the service account itself - it has no Workspace identity.

403 Request had insufficient authentication scopes. (ACCESS_TOKEN_SCOPE_INSUFFICIENT)

Cause: The access token was minted with a narrower scope list than the endpoint requires - typically calling users.tokens.list without admin.directory.user.security, or Reports without admin.reports.audit.readonly.

Fix: Add the missing scope both to the SCOPES list in your code and to the domain-wide delegation entry, then mint a fresh token; cached credentials keep the old scope set.

invalid_grant: Invalid email or User ID

Cause: GOOGLE_WORKSPACE_ADMIN_SUBJECT does not resolve to a real user in the tenant - usually a typo, a wrong domain, an alias that is not the primary address, or a deleted account.

Fix: Use the account's primary email exactly as shown in Admin console > Directory > Users. Aliases and secondary-domain addresses are unreliable for impersonation.

403 Admin SDK API has not been used in project 123456789012 before or it is disabled (SERVICE_DISABLED)

Cause: The API is not enabled in the Google Cloud project that owns the service account key.

Fix: Open the link in the error body, or go to APIs & Services > Library in that exact project, and enable Admin SDK API (plus Google Drive API and Google Workspace Alert Center API if used). Allow a minute for propagation.

429 Quota exceeded for quota metric 'Queries' and limit 'Queries per minute per user' (rateLimitExceeded)

Cause: Per-user token enumeration or unpaged Reports queries are firing thousands of serialised calls against the Admin SDK's per-minute quota.

Fix: Back off exponentially on 429 and 503, cap concurrency, scope the run to an org unit, and sample users rather than sweeping all of them on tenants above a few thousand seats.

200 OK with no "items" key from applications/drive

Cause: Not an error - the Drive audit log is unavailable on the tenant's licence tier (Business Starter) or the requested window predates data retention, so the Reports API returns an empty page.

Fix: Confirm the tenant is Business Standard or above before promising Drive coverage, and record the absence as an explicit coverage gap rather than reporting zero external sharing.

Official documentation: https://developers.google.com/workspace/admin/directory/v1/guides/delegation β†—

API operations this skill uses (19)

Every call the skill makes, linked to its official reference page.

OperationCallPermissionRef
List verified domains
Establishes the trust boundary: every domainName with verified=true is internal, everything else counts as external sharing.
GET /admin/directory/v1/customer/my_customer/domainshttps://www.googleapis.com/auth/admin.directory.domain.readonlydocs β†—
List all users with security attributes
Primary inventory - reads isAdmin, isDelegatedAdmin, isEnrolledIn2Sv, isEnforcedIn2Sv, suspended, archived, lastLoginTime and orgUnitPath for every account.
GET /admin/directory/v1/users?customer=my_customer&projection=full&maxResults=500https://www.googleapis.com/auth/admin.directory.user.readonlydocs β†—
Search users that are administrators
Fast path for the 'who has super admin' question on large tenants without paging the whole directory.
GET /admin/directory/v1/users?customer=my_customer&query=isAdmin%3Dtruehttps://www.googleapis.com/auth/admin.directory.user.readonlydocs β†—
Search users not enrolled in 2-Step Verification
Produces the 2SV gap list directly, and intersects with the admin list to yield the highest-severity finding.
GET /admin/directory/v1/users?customer=my_customer&query=isEnrolledIn2Sv%3Dfalsehttps://www.googleapis.com/auth/admin.directory.user.readonlydocs β†—
List admin roles
Maps roleId to a human role name and reveals custom roles that carry privileged permissions.
GET /admin/directory/v1/customer/my_customer/roleshttps://www.googleapis.com/auth/admin.directory.rolemanagement.readonlydocs β†—
List role assignments
Joins roles to identities and exposes scopeType (CUSTOMER vs ORG_UNIT) so customer-wide grants can be flagged as over-broad.
GET /admin/directory/v1/customer/my_customer/roleassignmentshttps://www.googleapis.com/auth/admin.directory.rolemanagement.readonlydocs β†—
List organizational units
Resolves an operator-supplied org unit path to the orgUnitID used to scope Reports queries and user enumeration.
GET /admin/directory/v1/customer/my_customer/orgunits?type=ALLhttps://www.googleapis.com/auth/admin.directory.orgunit.readonlydocs β†—
List a user's OAuth token grants
Authoritative current state of third-party app access - returns clientId, displayText, nativeApp and the exact scopes granted.
GET /admin/directory/v1/users/{userKey}/tokenshttps://www.googleapis.com/auth/admin.directory.user.securitydocs β†—
List OAuth authorize events
Fallback inventory when the user.security scope is withheld, and a timeline of when each client_id, app_name and scope was granted.
GET /admin/reports/v1/activity/users/all/applications/token?eventName=authorize&startTime={rfc3339}https://www.googleapis.com/auth/admin.reports.audit.readonlydocs β†—
List failed logins
Counts failures per account to detect password spray and credential stuffing bursts.
GET /admin/reports/v1/activity/users/all/applications/login?eventName=login_failure&startTime={rfc3339}https://www.googleapis.com/auth/admin.reports.audit.readonlydocs β†—
List suspicious logins flagged by Google
Surfaces Google's own risk verdicts, including suspicious_programmatic_login and suspicious_login_less_secure_app variants.
GET /admin/reports/v1/activity/users/all/applications/login?eventName=suspicious_login&startTime={rfc3339}https://www.googleapis.com/auth/admin.reports.audit.readonlydocs β†—
List 2-Step Verification disable events
A 2SV disable on an account that holds an admin role is a high-severity finding and a common post-compromise action.
GET /admin/reports/v1/activity/users/all/applications/login?eventName=2sv_disable&startTime={rfc3339}https://www.googleapis.com/auth/admin.reports.audit.readonlydocs β†—
List Drive document visibility changes
Detects files moved to public_on_the_web or people_with_link, reading the visibility, old_visibility, doc_id and doc_title parameters.
GET /admin/reports/v1/activity/users/all/applications/drive?eventName=change_document_visibility&startTime={rfc3339}https://www.googleapis.com/auth/admin.reports.audit.readonlydocs β†—
List Drive per-user access changes
Finds shares to a target_user or target_domain outside the verified domain set - the external-sharing signal that visibility changes miss.
GET /admin/reports/v1/activity/users/all/applications/drive?eventName=change_user_access&startTime={rfc3339}https://www.googleapis.com/auth/admin.reports.audit.readonlydocs β†—
List admin privilege grants
Shows who granted admin rights and when, so privilege changes can be matched against approved change windows. Repeat for ASSIGN_ROLE and CHANGE_TWO_STEP_VERIFICATION_ENFORCEMENT.
GET /admin/reports/v1/activity/users/all/applications/admin?eventName=GRANT_ADMIN_PRIVILEGE&startTime={rfc3339}https://www.googleapis.com/auth/admin.reports.audit.readonlydocs β†—
Get per-user security usage report
Independent reconciliation of the 2SV and super-admin counts derived from the Directory API; a mismatch means pagination dropped records.
GET /admin/reports/v1/usage/users/all/dates/{yyyy-mm-dd}?parameters=accounts:is_2sv_enrolled,accounts:is_super_adminhttps://www.googleapis.com/auth/admin.reports.usage.readonlydocs β†—
List shared drives as domain admin
Enumerates every shared drive in the tenant so their permissions can be swept at files depth.
GET https://www.googleapis.com/drive/v3/drives?useDomainAdminAccess=true&pageSize=100https://www.googleapis.com/auth/drive.readonlydocs β†—
List permissions on a drive or file
Confirms current exposure - permissions with type 'anyone' are public and type 'domain' outside the verified set are external.
GET https://www.googleapis.com/drive/v3/files/{fileId}/permissions?useDomainAdminAccess=true&supportsAllDrives=true&fields=permissions(id,type,role,domain,emailAddress)https://www.googleapis.com/auth/drive.readonlydocs β†—
List Alert Center alerts
Corroborates audit-log findings with Google's own alerts, such as government-backed attack warnings and leaked-password detections.
GET https://alertcenter.googleapis.com/v1beta1/alerts?pageSize=100https://www.googleapis.com/auth/apps.alertsdocs β†—