Collects Google Workspace identity and security posture metrics
epack install collector google-workspace
Adds to epack.yaml, resolves dependencies, downloads binary.
Run all configured collectors and build a pack:
epack collect
Runs all collectors in epack.yaml and outputs an evidence pack.
Or add manually to epack.yaml:
collectors:
google-workspace:
source: https://github.com/locktivity/epack-collector-google-workspace
Then run epack install to lock and sync.
The Google Workspace collector gathers security posture metrics from your Google Workspace tenant. It covers authentication strength, account hygiene, privileged access, password health, third-party app exposure, and positive-only Context-Aware Access deny evidence related to device state.
The collector always emits a detailed artifact, and conditionally emits a normalized artifact when the usage report contains data:
artifacts/google-workspace.json — detailed Google Workspace posture metrics (always emitted)artifacts/google-workspace.idp-posture.json — normalized evidencepack/idp-posture@v1 artifact for cross-provider comparison (omitted when the usage report is empty, e.g. for new tenants){
"schema_version": "1.0.0",
"collected_at": "2026-04-08T12:00:00Z",
"usage_report_date": "2026-04-07",
"provider": "google-workspace",
"org_domain": "example.com",
"customer_id": "C123abc",
"users": {
"total": 120,
"suspended": 10,
"archived": 5,
"locked_pct": 0.83,
"inactive_pct": 4.76,
"inactive_days": 90
},
"activity": {
"active_7d_pct": 78.33,
"active_30d_pct": 91.67
},
"authentication": {
"2sv_enrolled_pct": 92.5,
"2sv_enforced_pct": 88.33,
"2sv_protected_pct": 90.0,
"passkey_users_pct": 12.5,
"security_keys_total": 35
},
"admins": {
"privileged_users_count": 7,
"super_admin_count": 3,
"delegated_admin_count": 5,
"privileged_users_2sv_enrolled_pct": 85.71,
"privileged_users_2sv_enforced_pct": 100.0
},
"passwords": {
"weak_password_pct": 4.17,
"password_length_non_compliant_pct": 2.5
},
"apps": {
"authorized_apps_count": 47
},
"device_access": {
"lookback_days": 90,
"context_aware_access_denied_events": 12,
"device_state_denied_events": 4,
"managed_device_requirement_evidenced": true,
"access_context_manager": {
"access_policy_name": "accessPolicies/123456789",
"access_policy_parent": "organizations/987654321",
"basic_access_levels_count": 6,
"custom_access_levels_count": 1,
"basic_device_policy_access_levels_count": 2,
"basic_managed_device_access_levels_count": 1,
"basic_device_policy_access_level_titles": ["Corp Device", "High Trust Device"]
}
}
}
{
"schema_version": "1.0.0",
"collected_at": "2026-04-08T12:00:00Z",
"provider": "google_workspace",
"org_domain": "example.com",
"user_security": {
"mfa_coverage_pct": 90.0,
"mfa_phishing_resistant_pct": 12.5,
"inactive_pct": 4.76,
"locked_out_pct": 0.83,
"weak_password_pct": 4.17,
"password_policy_noncompliant_pct": 2.5
},
"app_security": {
"authorized_third_party_apps_count": 47
},
"privileged_access": {
"privileged_users_count": 7,
"super_admin_count": 3,
"privileged_mfa_coverage_pct": 85.71,
"standing_privileged_users_count": 7
},
"policy": {
"mfa_required": false,
"mfa_required_coverage_pct": 88.33,
"legacy_auth_blocked": true
},
"lifecycle": {
"suspended_pct": 8.33,
"archived_pct": 4.17
},
"device_access": {
"managed_device_required": true
}
}
The normalized artifact follows the evidencepack/idp-posture@v1 schema, matching the same shape used by the Okta and other IDP collectors.
policy.legacy_auth_blocked is emitted as true for Google Workspace because Google no longer supports less secure username/password-only app access for Workspace accounts.
| Normalized field | Google Workspace source | Notes |
|---|---|---|
mfa_coverage_pct |
2sv_protected_pct |
Effective MFA coverage, not just enrollment |
mfa_phishing_resistant_pct |
passkey_users_pct |
Passkey adoption as phishing-resistant proxy |
weak_password_pct |
weak_password_pct |
Weak password coverage comes directly from the usage report |
password_policy_noncompliant_pct |
password_length_non_compliant_pct |
Tracks users that fail the tenant password length policy |
app_security.authorized_third_party_apps_count |
authorized_apps_count |
OAuth-connected third-party application count |
privileged_access.privileged_users_count |
privileged_users_count |
Counts unique active privileged users only |
privileged_access.super_admin_count |
super_admin_count |
Excludes suspended and archived admins |
privileged_access.privileged_mfa_coverage_pct |
privileged_users_2sv_enrolled_pct |
Percent of active privileged users with 2SV enrolled |
privileged_access.standing_privileged_users_count |
privileged_users_count |
Google Workspace has standing admin roles in this collector model |
policy.mfa_required |
2sv_enforced_pct == 100 |
Only true when all users have 2SV enforced by policy |
policy.mfa_required_coverage_pct |
2sv_enforced_pct |
Preserves partial enforcement coverage instead of dropping the policy block |
policy.legacy_auth_blocked |
Google Workspace platform behavior | Google Workspace no longer supports less secure username/password apps for tenant access |
lifecycle.suspended_pct |
suspended / total |
Uses the usage report's suspended user count |
lifecycle.archived_pct |
archived / total |
Uses the usage report's archived user count |
device_access.managed_device_required |
device_access.managed_device_requirement_evidenced |
Emitted only when Context-Aware Access deny events include a device-state parameter |
app_security.sso_coverage_pct, app_security.provisioning_enabled_pct, and app_security.deprovisioning_enabled_pct are omitted because the Reports and Directory APIs do not expose per-app SSO or provisioning settings. policy.session_lifetime_max_min, policy.idle_timeout_max_min, policy.phishing_resistant_required_for_privileged, and device_access.managed_device_required_for_admins are also omitted because those tenant controls are not available through these APIs in a reliable tenant-wide form.
device_access.managed_device_required is positive-only evidence derived from Context-Aware Access audit logs. The collector sets it only when Google reports ACCESS_DENY_EVENT entries whose parameters include a device-state field. This is evidence that a device-state access level fired, not proof of complete policy coverage, and Google does not emit grant events for clean passes. Context-Aware Access monitor mode can also produce similar deny-style events, so the collector surfaces this as evidence with an explicit diagnostics caveat rather than as a full configuration truth.
When organization_id or access_policy is configured and the service account has roles/accesscontextmanager.policyReader, the detailed artifact also includes an access_context_manager summary under device_access. This summarizes basic access levels with devicePolicy conditions and counts any custom CEL access levels, but it still does not prove which Workspace apps those levels are attached to.
User population and account health.
| Metric | Why It Matters |
|---|---|
total |
Tenant size. Total user accounts including suspended and archived. |
suspended |
Disabled accounts. Users administratively suspended from the tenant. |
archived |
Retained accounts. Users archived but not deleted, often for compliance retention. |
locked_pct |
Potential attack indicator. Spikes in lockout rates may indicate brute force or credential stuffing attacks. |
inactive_pct |
Orphan account risk. Inactive accounts (90+ days no login) are prime targets for attackers. They may belong to departed employees or unused service accounts. |
inactive_days |
Inactivity threshold. The number of days used to classify a user as inactive (default: 90). |
Login activity rates across the tenant.
| Metric | Why It Matters |
|---|---|
active_7d_pct |
Short-term engagement. Percentage of users who logged in within the last 7 days. Low values may indicate shadow IT or alternative access paths. |
active_30d_pct |
Monthly engagement. Percentage of users active in the last 30 days. A large gap between this and 7-day activity may indicate periodic or seasonal users. |
Two-step verification and phishing-resistant credential adoption.
| Metric | Why It Matters |
|---|---|
2sv_enrolled_pct |
Account takeover protection. 2-step verification significantly reduces credential-based attacks. Low enrollment leaves accounts vulnerable to password spraying and phishing. |
2sv_enforced_pct |
Policy enforcement. Users with 2SV enforced by admin policy, not just voluntarily enrolled. The gap between enrolled and enforced indicates reliance on user opt-in. |
2sv_protected_pct |
Effective protection. Users actually protected by 2SV (enrolled, enforced, and no bypass). This is the strongest signal of real MFA coverage. |
passkey_users_pct |
Passwordless adoption. Passkeys are phishing-resistant by design. Tracks progress toward eliminating password-based authentication. |
security_keys_total |
Hardware key inventory. Total registered security keys (FIDO2/U2F). Useful for tracking hardware rollout across the organization. |
Privileged account exposure and enforcement.
| Metric | Why It Matters |
|---|---|
privileged_users_count |
Unique privileged identities. Counts active users with any privileged role without double-counting users that hold multiple admin role types. |
super_admin_count |
Blast radius. Super admins have unrestricted access. Fewer is better. Industry guidance recommends 2-4 for business continuity without excess risk. |
delegated_admin_count |
Delegated access scope. Delegated admins have subset privileges. High counts may indicate over-delegation. |
privileged_users_2sv_enrolled_pct |
Admin MFA coverage. Tracks whether privileged users have actually enrolled in 2SV, which is the closest Google Workspace signal to real privileged MFA coverage. |
privileged_users_2sv_enforced_pct |
Admin MFA enforcement. The single most important metric for third-party risk. 100% means all active privileged users have 2SV enforced by policy. |
Password hygiene across the tenant.
| Metric | Why It Matters |
|---|---|
weak_password_pct |
Credential strength. Users with passwords classified as weak by Google. Indicates exposure to brute force and dictionary attacks. |
password_length_non_compliant_pct |
Policy compliance. Users whose passwords don't meet the configured minimum length requirement. Non-zero values indicate policy enforcement gaps. |
Third-party application exposure.
| Metric | Why It Matters |
|---|---|
authorized_apps_count |
OAuth exposure surface. Number of third-party apps authorized to access tenant data. High counts increase data exfiltration risk and expand the supply chain attack surface. |
Context-Aware Access deny evidence.
| Metric | Why It Matters |
|---|---|
lookback_days |
Evidence window. Number of days of audit history searched for device-state-based denials. |
context_aware_access_denied_events |
Control activity. Total Context-Aware Access deny events seen in the audit window. |
device_state_denied_events |
Managed-device evidence. Denials that specifically included a device-state parameter, indicating device posture was part of the access decision. |
managed_device_requirement_evidenced |
Positive-only signal. true means Google audit logs show device-state access levels firing somewhere in the tenant; it is not proof of full policy coverage, and monitor-mode events can look similar. |
access_context_manager.* |
Config enrichment. Optional Google Cloud access-level summary that helps you see whether the org has basic device-policy access levels configured, even though Workspace-side app assignment is still API-thin. |
The collector uses a Google service account with domain-wide delegation. This is the standard pattern for automated access to Google Workspace admin data.
SERVICE_DISABLED if the API isn't enabled in this project.| API | Needed for |
|---|---|
| Admin SDK API (admin.googleapis.com) | All levels (users, reports, and at audit+ groups, org units, devices, domains, roles) |
| Groups Settings API (groupssettings.googleapis.com) | audit / internal: per-group external-members and who-can-* settings |
| Cloud Identity API (cloudidentity.googleapis.com) | audit / internal: SAML SSO profiles |
| Google Vault API (vault.googleapis.com) | audit / internal: Vault matters / holds (also needs a Vault license) |
| Access Context Manager API (accesscontextmanager.googleapis.com) | Optional device_access enrichment (when organization_id / access_policy is set) |
For a trust-only run, the Admin SDK API is sufficient. The other APIs are
only called at audit / internal; a disabled one degrades just that surface
(a diagnostic names the API, the rest of the run continues).
epack-collector)In the Google Workspace Admin Console:
https://www.googleapis.com/auth/admin.directory.user.readonlyhttps://www.googleapis.com/auth/admin.directory.customer.readonlyhttps://www.googleapis.com/auth/admin.reports.usage.readonlyhttps://www.googleapis.com/auth/admin.reports.audit.readonly (recommended, enables device_access evidence from Context-Aware Access audit logs)Set admin_email to a Google Workspace admin account. The service account impersonates this user to access admin APIs. The account needs permission to read users, customer metadata, usage reports, and optionally audit activity for Context-Aware Access.
If you want the detailed artifact to include Access Context Manager access-level config alongside Context-Aware Access deny evidence:
roles/accesscontextmanager.policyReader) on the relevant Google Cloud organization.organization_id to let the collector discover the access policy under organizations/{id}, oraccess_policy to an explicit Access Context Manager policy resource like accessPolicies/123456789 (or just the numeric ID).This enrichment is optional. It improves the detailed device_access section, but the normalized artifact remains conservative because Workspace-side app assignment and monitor-mode details are not fully exposed by API.
| Option | Required | Description |
|---|---|---|
admin_email |
Yes | Google Workspace admin account to impersonate |
customer |
No | Google customer key (defaults to my_customer, which resolves to your tenant) |
organization_id |
No | Google Cloud organization ID used to discover an Access Context Manager policy for device_access config enrichment |
access_policy |
No | Explicit Access Context Manager policy resource or numeric ID. Takes precedence over organization_id |
| Variable | Description |
|---|---|
GOOGLE_SERVICE_ACCOUNT_JSON |
Full JSON content of the service account key file |
GOOGLE_SERVICE_ACCOUNT_JSON environment variable contains the full JSON key file content (not a file path)admin_email is a real Google Workspace admin account (not a regular user)device_access evidence and adds a warning to diagnosticsroles/accesscontextmanager.policyReader on the organization and that the organization_id or access_policy value is correctusage_report_date field in the output shows which day the report covers.stream: myorg/google-workspace-posture
collectors:
google-workspace:
source: locktivity/epack-collector-google-workspace@^0.2
config:
admin_email: admin@example.com
secrets:
- GOOGLE_SERVICE_ACCOUNT_JSON
Then run:
export GOOGLE_SERVICE_ACCOUNT_JSON="$(cat /path/to/service-account.json)"
epack collect
See Configuration for service account setup instructions.
If your service account has access to a specific Google Workspace tenant, set the customer key explicitly:
collectors:
google-workspace:
source: locktivity/epack-collector-google-workspace@^0.2
config:
customer: C0123abc
organization_id: "987654321"
admin_email: admin@example.com
secrets:
- GOOGLE_SERVICE_ACCOUNT_JSON
The collector always emits the detailed artifact. The normalized artifact is included when the usage report contains data (omitted for new tenants or delayed reports).
artifacts/google-workspace.json{
"schema_version": "1.0.0",
"collected_at": "2026-04-08T12:00:00Z",
"usage_report_date": "2026-04-07",
"provider": "google-workspace",
"org_domain": "example.com",
"customer_id": "C123abc",
"users": {
"total": 120,
"suspended": 10,
"archived": 5,
"locked_pct": 0.83,
"inactive_pct": 4.76,
"inactive_days": 90
},
"activity": {
"active_7d_pct": 78.33,
"active_30d_pct": 91.67
},
"authentication": {
"2sv_enrolled_pct": 92.5,
"2sv_enforced_pct": 88.33,
"2sv_protected_pct": 90.0,
"passkey_users_pct": 12.5,
"security_keys_total": 35
},
"admins": {
"privileged_users_count": 7,
"super_admin_count": 3,
"delegated_admin_count": 5,
"privileged_users_2sv_enrolled_pct": 85.71,
"privileged_users_2sv_enforced_pct": 100.0
},
"passwords": {
"weak_password_pct": 4.17,
"password_length_non_compliant_pct": 2.5
},
"apps": {
"authorized_apps_count": 47
},
"device_access": {
"lookback_days": 90,
"context_aware_access_denied_events": 12,
"device_state_denied_events": 4,
"managed_device_requirement_evidenced": true,
"access_context_manager": {
"access_policy_name": "accessPolicies/123456789",
"access_policy_parent": "organizations/987654321",
"basic_access_levels_count": 6,
"custom_access_levels_count": 1,
"basic_device_policy_access_levels_count": 2,
"basic_managed_device_access_levels_count": 1,
"basic_device_policy_access_level_titles": ["Corp Device", "High Trust Device"]
}
}
}
artifacts/google-workspace.idp-posture.json{
"schema_version": "1.0.0",
"collected_at": "2026-04-08T12:00:00Z",
"provider": "google_workspace",
"org_domain": "example.com",
"user_security": {
"mfa_coverage_pct": 90.0,
"mfa_phishing_resistant_pct": 12.5,
"inactive_pct": 4.76,
"locked_out_pct": 0.83,
"weak_password_pct": 4.17,
"password_policy_noncompliant_pct": 2.5
},
"app_security": {
"authorized_third_party_apps_count": 47
},
"privileged_access": {
"privileged_users_count": 7,
"super_admin_count": 3,
"privileged_mfa_coverage_pct": 85.71,
"standing_privileged_users_count": 7
},
"policy": {
"mfa_required": false,
"mfa_required_coverage_pct": 88.33,
"legacy_auth_blocked": true
},
"lifecycle": {
"suspended_pct": 8.33,
"archived_pct": 4.17
},
"device_access": {
"managed_device_required": true
}
}
All coverage values are percentages (0-100). See Overview for detailed metric descriptions.
name: Collect Google Workspace Posture
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
jobs:
collect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install epack
run: |
curl -sSL https://install.epack.dev | bash
- name: Collect evidence
run: epack collect --frozen
env:
GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}
Store the service account JSON as a repository secret named GOOGLE_SERVICE_ACCOUNT_JSON.
If a single service account has domain-wide delegation across tenants, use the customer key to target each one in its own epack.yaml:
# epack-prod.yaml
collectors:
google-workspace:
source: locktivity/epack-collector-google-workspace@^0.2
config:
customer: C0abc123
admin_email: admin@company.com
secrets:
- GOOGLE_SERVICE_ACCOUNT_JSON
# epack-subsidiary.yaml
collectors:
google-workspace:
source: locktivity/epack-collector-google-workspace@^0.2
config:
customer: C0def456
admin_email: admin@subsidiary.com
secrets:
- GOOGLE_SERVICE_ACCOUNT_JSON
export GOOGLE_SERVICE_ACCOUNT_JSON="$(cat service-account.json)"
epack collect -c epack-prod.yaml
epack collect -c epack-subsidiary.yaml
If each tenant requires its own service account, run separate invocations with different credentials:
GOOGLE_SERVICE_ACCOUNT_JSON="$(cat prod-sa.json)" epack collect -c epack-prod.yaml
GOOGLE_SERVICE_ACCOUNT_JSON="$(cat subsidiary-sa.json)" epack collect -c epack-subsidiary.yaml
**Full Changelog**: https://github.com/locktivity/epack-collector-google-workspace/compare/v0.1.2...v0.2.0
**Full Changelog**: https://github.com/locktivity/epack-collector-google-workspace/compare/v0.1.1...v0.1.2
**Full Changelog**: https://github.com/locktivity/epack-collector-google-workspace/compare/v0.1.0...v0.1.1
**Full Changelog**: https://github.com/locktivity/epack-collector-google-workspace/commits/v0.1.0