Skip to content

About

Google Workspace Identity Security Posture Dashboard — collects MFA, OAuth, login, and account event data via Admin SDK and renders a self-contained HTML report

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

Google Workspace Identity Security Posture Dashboard

A Python CLI tool that pulls identity security data from the Google Workspace Admin SDK and generates a self-contained HTML dashboard — no web server required.

What it covers:

Data point Source
MFA enrollment & enforcement per user Directory API
Password policy (org-level settings + per-user compliance) Directory API / Usage Report
Login activity — auth methods, 2FA challenges, suspicious logins Reports API
MFA enable/disable event history Reports API
Third-party OAuth app grants — scope risk level + history Directory API / Reports API
Password change events Reports API
Account recovery info change events Reports API

Output is a single .html file you can open in any browser or share with auditors.

![Dashboard screenshot placeholder — shows 9 KPI cards + tabbed detail views]


Prerequisites

  • Python 3.11+
  • A Google Workspace super-admin account (Business Starter / Standard / Plus / Enterprise)
  • A Google Cloud project (the free tier is sufficient — this tool makes read-only API calls)

Setup — 6 Steps

Step 1 — Create a Google Cloud Project

  1. Go to console.cloud.google.com
  2. Click the project selector → New Project
  3. Name it workspace-posture (or anything you like)

Step 2 — Enable the Admin SDK API

  1. In your project, go to APIs & Services → Library
  2. Search for Admin SDK API
  3. Click Enable

This single enable covers the Directory, Reports, and Token sub-APIs.


Step 3 — Create a Service Account

  1. Go to IAM & Admin → Service Accounts
  2. Click Create Service Account
    • Name: identity-posture-sa
    • Description: Reads Workspace identity data for security posture reporting
  3. Click Create and continue, then Skip the next two optional steps, then Done
  4. Click the service account you just created → Keys tab
  5. Click Add Key → Create new key → JSON → Download and save the file
  6. Place the file at credentials/service-account.json in this project directory
  7. Still on the service account details page, note the Unique ID — a long numeric string (e.g. 112233445566778899001). You'll need this in the next step.

Warning: The Unique ID is the long number on the service account details page. It is not the SHA-1 fingerprint shown on the Keys tab.


Step 4 — Configure Domain-Wide Delegation

This grants the service account read-only access to your Workspace data.

  1. Go to admin.google.com signed in as a super-admin
  2. Navigate to: Security → Access and data control → API controls
  3. Click Manage Domain Wide Delegation
  4. Click Add new
  5. Fill in:
    • Client ID: the numeric Unique ID from Step 3
    • OAuth Scopes: copy and paste the entire line below
      https://www.googleapis.com/auth/admin.directory.user.readonly,https://www.googleapis.com/auth/admin.reports.audit.readonly,https://www.googleapis.com/auth/admin.reports.usage.readonly,https://www.googleapis.com/auth/admin.directory.user.security,https://www.googleapis.com/auth/admin.directory.customer.readonly
      
  6. Click Authorise

Allow up to 5 minutes for the delegation to propagate before running the tool.

Why these scopes?
Scope Used for
admin.directory.user.readonly List users and read real-time MFA status (isEnrolledIn2Sv, isEnforcedIn2Sv)
admin.reports.audit.readonly Login events, MFA disable events, OAuth grant/revoke events, password change events
admin.reports.usage.readonly Per-user password compliance and MFA snapshot from usage report
admin.directory.user.security Read OAuth tokens currently granted per user
admin.directory.customer.readonly Read org-level password policy settings

Step 5 — Python Environment Setup

# Clone the repo
git clone https://github.com/YOUR_USERNAME/identity-posture.git
cd identity-posture

# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

Step 6 — Configure Environment Variables

cp .env.example .env

Edit .env and fill in your values:

# Path to the service account JSON key (from Step 3)
GOOGLE_SERVICE_ACCOUNT_FILE=credentials/service-account.json

# Email of a super-admin account to impersonate via domain-wide delegation
GOOGLE_DELEGATED_ADMIN=admin@yourdomain.com

# Your Google Workspace primary domain
GOOGLE_DOMAIN=yourdomain.com

# SQLite database path (created automatically on first run)
DB_PATH=data/posture.db

# Directory where HTML reports are written
REPORT_OUTPUT_DIR=reports/

# Lookback windows for event history (in days)
LOGIN_LOOKBACK_DAYS=7
MFA_LOOKBACK_DAYS=30
OAUTH_LOOKBACK_DAYS=30
USER_ACCOUNT_LOOKBACK_DAYS=30

Security: .env and credentials/*.json are in .gitignore and must never be committed.


Running the Tool

Collect all data and generate the dashboard

python main.py run

Add --open to open the report in your browser automatically:

python main.py run --open

Selective collection

# Regenerate the HTML report from already-stored data (no API calls)
python main.py report --open

# Only collect data, skip report generation
python main.py collect

# Collect only specific data sources
python main.py run --collectors users,logins,mfa

Available collectors: users, policy, logins, mfa, oauth, user_accounts

Output files

File Description
reports/dashboard_latest.html Always the most recent run — share this with auditors
reports/dashboard_YYYYMMDD_HHMMSS.html Timestamped archive of each run
data/posture.db SQLite database with all collected data

Dashboard Overview

The dashboard opens with a landing page of 9 KPI cards covering the most security-relevant signals at a glance:

Card Green when Red/Amber when
MFA Enrolled % 100% < 100%
MFA Protected % 100% < 100%
Admins Without MFA 0 > 0 (critical)
Password Non-Compliant 0 > 0
Suspicious Logins 0 > 0
High-Risk OAuth Apps 0 > 0
Password Changes — > 0 shown in amber (informational)
Recovery Info Changes — > 0 shown in amber (potential persistence signal)
MFA Disabled Events 0 > 0 shown in red

Detail tabs:

Tab What it shows
Password Policy Org-level settings (min length, complexity, reuse, expiry) + per-user compliance
MFA Status Enrolled / Enforced / Protected columns per user; admin alert if any admin lacks MFA
Login Activity Login events with auth method, 2FA challenge type, success/failure, suspicious flag
MFA Events Timeline of MFA enroll and disable events (user-initiated and admin-initiated)
OAuth Apps Active OAuth grants with scope risk rating (HIGH / MEDIUM / LOW) + grant/revoke history
Account Events Password changes and account recovery info changes by user and actor

Scheduled / Automated Runs

For consistent data, run the tool at least once per day. Event data (logins, MFA events, OAuth events) is typically available via the API within 1–2 hours of the event occurring.

macOS / Linux — cron

# Edit your crontab
crontab -e

# Run daily at 06:00, log output to a file
0 6 * * * cd /path/to/identity-posture && /path/to/.venv/bin/python main.py run >> logs/cron.log 2>&1

macOS — launchd (runs even after reboot)

Create ~/Library/LaunchAgents/com.identity-posture.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.identity-posture</string>
    <key>ProgramArguments</key>
    <array>
        <string>/path/to/.venv/bin/python</string>
        <string>/path/to/identity-posture/main.py</string>
        <string>run</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>6</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <key>WorkingDirectory</key>
    <string>/path/to/identity-posture</string>
    <key>StandardOutPath</key>
    <string>/path/to/identity-posture/logs/launchd.log</string>
    <key>StandardErrorPath</key>
    <string>/path/to/identity-posture/logs/launchd.log</string>
</dict>
</plist>

Load it:

launchctl load ~/Library/LaunchAgents/com.identity-posture.plist

Google Cloud Run Job (unattended, off-laptop)

For fully automated, off-laptop execution:

  1. Containerise with the provided Dockerfile (if present) or build one based on python:3.12-slim
  2. Store credentials/service-account.json in Google Secret Manager
  3. Deploy as a Cloud Run Job in your GCP project
  4. Schedule it with Cloud Scheduler (cron syntax, calls the Cloud Run Job trigger)
  5. Mount the secret as an environment variable or file at startup
  6. Write reports to Google Cloud Storage and serve via a signed URL or Cloud Run web service

This requires no additional IAM setup if the Cloud Run service account is granted the same domain-wide delegation as the local service account.


Data Limitations

MFA Status

  • Enrolled (isEnrolledIn2Sv) — whether a second factor is registered. Comes from the Directory API; real-time.
  • Enforced (isEnforcedIn2Sv) — whether 2FA is enforced on the account. Comes from the Directory API; real-time.
  • Protected — whether 2FA was actually challenged at last sign-in (from userUsageReport). A user can be enrolled but bypass 2FA via trusted devices or SSO.
  • userUsageReport data is typically 24–48 hours behind. New Workspace deployments may have no data yet.

Passwords

Google does not expose password hashes or plaintext via any API. This tool can show:

  • Org-level policy settings (minimum length, complexity toggle, reuse, expiry)
  • Per-user compliance (password_length_ok) sourced from userUsageReport
  • Limitation: Users whose passwords were set via GCDS hash sync will show Unknown for compliance — this is a Google design decision, not a tool limitation

Login Activity and Events

  • Reports API data has a minimum 1–2 hour ingestion lag
  • Historical data is available for up to 180 days via the API
  • The is_suspicious flag is set by Google's risk assessment, not by this tool

OAuth Tokens

  • Current grants show what is currently authorised, not when tokens were last used
  • An authorised app may be dormant

Troubleshooting

403 Forbidden errors

  • Verify domain-wide delegation was added correctly in Step 4
  • Confirm GOOGLE_DELEGATED_ADMIN is a super-admin email (not a regular user or delegated admin)
  • Allow up to 5 minutes after configuring delegation for propagation
  • Confirm the Admin SDK API is enabled in your GCP project (Step 2)

Invalid Client ID when adding delegation

  • Make sure you're entering the numeric Unique ID from the service account details page, not the SHA-1 key fingerprint shown on the Keys tab

FileNotFoundError for service account

  • Confirm the JSON file is at credentials/service-account.json (or the path in GOOGLE_SERVICE_ACCOUNT_FILE)

MFA showing 0% enrolled when you know users are enrolled

  • Check that GOOGLE_DELEGATED_ADMIN is an account on your primary domain (the one that owns the Workspace subscription), not a secondary/alias domain
  • The tool reads MFA status from the Directory API; delegating through a secondary domain can return incomplete results

Empty userUsageReport results

  • Usage reports are typically available for the previous 2 days
  • New Workspace deployments may have no historical data yet

quota exceeded errors

  • The tool avoids per-user filtered queries for event data, but very large orgs (10,000+ users) may still hit limits
  • Consider running during off-peak hours
  • Reduce LOGIN_LOOKBACK_DAYS or OAUTH_LOOKBACK_DAYS

Architecture Notes

main.py (CLI)
  └── collectors/
  │     ├── users.py              Directory API users.list()      → users table
  │     ├── password_policy.py    Directory API customers.get()   → password_policy table
  │     ├── login_activity.py     Reports API login application   → login_events table
  │     ├── mfa_events.py         Reports API admin application   → mfa_events table
  │     ├── oauth_tokens.py       Directory API tokens.list()     → oauth_tokens table
  │     │                         Reports API token application   → oauth_events table
  │     └── user_account_events.py Reports API user_accounts app → user_account_events table
  └── storage/
  │     ├── schema.sql            SQLite schema (auto-created)
  │     └── db.py                 upsert / query helpers
  └── dashboard/
        ├── renderer.py           Pulls from SQLite, renders Jinja2 template
        └── templates/index.html  Self-contained HTML (Tailwind CSS + vanilla JS)
  • No web server required — the dashboard is a single static HTML file
  • SQLite stores history across runs — re-running only appends new events; INSERT OR IGNORE on event IDs prevents duplicates
  • OAuth tokens are fetched per-user (tokens.list) in parallel with a thread pool capped at 10 workers; each thread builds its own API client to avoid connection sharing issues
  • Event collectors use broad time-range queries (no per-user filter) to stay within the Reports API filter quota cap of 250 requests/minute
  • Credentials never leave your machine — the service account key and .env are .gitignored; the generated HTML contains no credentials

Security Considerations

  • The service account key JSON is a long-lived credential — treat it like a password. Store it securely and rotate it if it may have been exposed.
  • Domain-wide delegation grants broad read access to your Workspace data. Audit who can access the machine where this tool runs.
  • The generated HTML reports contain user email addresses, login activity, and OAuth grant details. Treat them as confidential and restrict access accordingly.
  • Consider using Workload Identity Federation instead of a service account key if running in GCP — eliminates the need for a key file entirely.

License

MIT

About

Google Workspace Identity Security Posture Dashboard — collects MFA, OAuth, login, and account event data via Admin SDK and renders a self-contained HTML report

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages