From 7dc5bbae2d703832e144ed04da77a5a0b4126d7f Mon Sep 17 00:00:00 2001 From: Sripathi Krishnan Date: Fri, 23 Jan 2026 14:28:22 +0530 Subject: [PATCH 1/4] Improve CLAUDE.md documentation and enable branch builds - Restructure User Flow into Setup, Runtime, and Token Refresh sections - Add Organization Setup section as prerequisite - Update Skills section with Agent Skills standard reference - Simplify Testing section to use cloud-based workflow - Enable Docker builds for all branches (not just main) - Update CI/CD docs to reflect branch tagging Co-Authored-By: Claude Opus 4.5 --- .github/workflows/docker-publish.yml | 2 +- CLAUDE.md | 124 ++++++++++++++++++--------- 2 files changed, 83 insertions(+), 43 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 8a7be988..7b498b06 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -3,7 +3,7 @@ name: Build and Publish Docker Image on: push: branches: - - main + - '**' tags: - 'v*' pull_request: diff --git a/CLAUDE.md b/CLAUDE.md index 642a4fbe..b4322b46 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,33 +1,62 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - ## Project Overview +ExtraSuite is an open source project (https://github.com/think41/extrasuite) that enables an AI agent such as claude code or codex to get temporary service account tokens on behalf of the user so that it can read or edit google sheets, docs or slides. Each end user gets their own private service account. `extrasuite-server` generates this service account on first access and returns a short lived token. End users must share the google drive file with this service account, and then instruct their AI agent to access the file. This ensures the AI agent has temporary access to the files. It also ensures that any edits made by the AI show up in the version history clearly attributed to the service account email, thereby maintaining an audit trail. -ExtraSuite is a headless authentication service for CLI tools accessing Google Workspace APIs. It enables users to obtain short-lived service account tokens for interacting with Google Sheets, Docs, and Drive. +This project publishes a public docker image on google cloud artifact registry. Each organization or group that wishes to use this project must deploy the container via google cloud run in a google cloud project. In addition, to authenticate end users belonging to that organization or group, they must setup OAuth credentials in google cloud. +## Packages The project consists of three packages: -1. **extrasuite-server** - Containerized fastapi based application to provide employee specific service account and short lived acccess tokens that can be used to call google drive/sheets/docs/slides APIs. It also has minimal UI to allow employees to install skills via a command line installation command. -2. **extrasuite-client** - Package that has a CLI based application to call extrasuite-server on behalf of an LLM based agent and provide it shortlived access tokens. -3. **website** - mkdocs based documentation website, hosted on github pages, automatically deployed to https://extrasuite.think41.com on every commit to main branch. +1. **extrasuite-server** - Containerized FastAPI application to provide employee-specific service accounts and short-lived access tokens that can be used to call google drive/sheets/docs/slides APIs. It also has minimal UI to allow employees to install skills via a command line installation command. +2. **extrasuite-client** - CLI based application to call extrasuite-server from an AI Agent and provide it shortlived access tokens. This will eventually be published to a pypi package, but currently extrasuite-client/src/extrasuite_client/credentials.py is manually copied by the projects that wish to use it. +3. **website** - mkdocs based documentation website, hosted on github pages, automatically deployed to https://extrasuite.think41.com on every commit to main branch. The website also has instructions to deploy, end user documentation and other product usage. + +## Skills for Slides, Docs and Sheets Specific Packages +The AI agent needs instructions on how to read or edit google drive files. These are provided as "Agent Skills" which are an open standard. See https://agentskills.io/home. At its core, a skill is a markdown file /SKILL.md that is saved by end users at a well known agent specific location. The skills are distributed by extrasuite-server, see extrasuite-server/skills. + +In addition to instructions, the AI agent needs python libraries to manipulate files. We have 3 related open source python projects. On a developer machine, each of these projects is cloned in folders parallel to the root of this project. + +The SKILL.md file explains how to use `extrasuite-client` to get the temporary service account token, and then use one of the following projects to read or edit the specific file type. + +1. **gsheetx** - See https://github.com/think41/gsheetx, forked from gspread, provides methods to manipulate google sheets. +1. **gslidex** - See https://github.com/think41/gslidex. "Pulls" google slides into an XML file called Slide Markup Language or SML. AI agents make edits to this XML file. A "diff" process identifies the exact changes that need to be carried out, and then "push" invokes appropriate google slides API to ensure the diffs are applied. This gives AI agents a simpler model to edit google slides. This library is alpha quality. +1. **gdocx** - See https://github.com/think41/gdocx. Similar workflow to gslidex, but the intermediate format is an HTML file representing the google doc. This is under development and not meant for end users yet. + +Currently, these three packages haven't been published to PyPI. Only the gsheetx skill is working, and it uses the underlying gspread library directly. The wrapper code is in `extrasuite-server/skills/gsheetx/gsheet_utils.py`, which will be replaced once gsheetx is published to PyPI. + +## Organization Setup (prerequisite) + +Before end users can use ExtraSuite, an administrator must deploy extrasuite-server for their organization: + +1. **Create a Google Cloud project** with billing enabled +2. **Enable required APIs**: IAM, Service Account Credentials, Firestore, Drive, Sheets, Docs, Slides +3. **Configure OAuth consent screen** and create OAuth 2.0 credentials (Web application type) +4. **Create a Firestore database** in the project +5. **Deploy extrasuite-server to Cloud Run** using the public image: `asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server` +6. **Set environment variables** on Cloud Run: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_CLOUD_PROJECT`, `SECRET_KEY` +7. **Share the Cloud Run URL** with end users in the organization + +See `website/docs/deployment/` for detailed deployment instructions. ## User Flow -1. User logs in to the **extrasuite-server** using OAuth via their google workspace or gmail account -2. This creates a 1:1 service acccount for the employee, and grants this service account read permissions for google drive API and read/write permissions to slides/docs/sheets API. We don't create credentials for this service account. -3. User copies the ` | sh` script to install the skill. -4. User instructs agent to access the sheet -5. Agent calls `CredentialsManager` in `extrasuite-client` to get a short lived access token -6. `CredentialsManager` returns the cached token if available, otherwise -7. `CredentialsManager` starts a http server on random port, then opens browser to `/api/token/auth?port=`. -8. Alternatively, it prints the URL and asks user to authenticate. -9. User is redirected to google to authenticate, and then redirected back to `/api/auth/callback` after authentication -10. `extrasuite-server` `/api/auth/callback` is invoked. It redirects the browser back to http://localhost:/on-authentication?code= and/or displays the to the user. -11. `CredentialsManager` then calls `/api/token/exchange` with the auth code to get the token. -12. At this point, `extrasuite-server` impersonates the user specific service account using server credentials. Then it returns a short lived access token back to the `CredentialsManager` in `extrasuite-client`. -13. `CredentialsManager` saves the token + service account email + expiry on disk with appropriate linux permissions. -14. `CredentialsManager` provides the token + service account email to the LLM Agent -15. LLM Agent then writes python code to make API calls to google sides/sheets/docs/drive directly from the user's device -16. Once the token expires, the same flow repeats. If the user has a valid session with `extrasuite-server` - the browser will open but authentication against google server will be skipped. This will result in browser opening and closing in a few seconnds. + +### One-Time Setup (per user) +1. User logs in to **extrasuite-server** via OAuth using their Google Workspace or Gmail account +2. Server creates a dedicated service account for this user, granting it read access to Google Drive API and read/write access to Sheets/Docs/Slides APIs (no credentials are stored for this service account) +3. User copies the skill installation command from the server UI and runs it to install the skill into their AI agent (e.g., Claude Code's `~/.claude/commands/` directory) + +### Runtime Flow (each time agent needs access) +4. User shares a Google Drive file with their service account email, then instructs the agent to access it +5. Agent invokes the skill, which calls `CredentialsManager` (in `extrasuite-client`) to get a short-lived access token +6. If a valid cached token exists in `~/.config/extrasuite/token.json`, it's returned immediately +7. Otherwise, `CredentialsManager` starts a local HTTP server on a random port and opens the browser to `/api/token/auth?port=` (or prints the URL if browser launch fails) +8. User authenticates with Google, then is redirected to `/api/auth/callback` +9. Server redirects browser to `http://localhost:/on-authentication?code=` (also displays the code for manual entry if needed) +10. `CredentialsManager` exchanges the auth code via `/api/token/exchange` +11. Server uses domain-wide delegation to impersonate the user's service account and returns a short-lived access token +12. `CredentialsManager` caches the token locally (with 600 permissions) and provides it to the agent +13. Agent uses the token to make Google API calls directly from the user's device + +### Token Refresh +When the token expires, the runtime flow repeats from step 7. If the user still has a valid session with extrasuite-server, the browser opens briefly and closes automatically (no re-authentication required). ## Development Commands @@ -83,28 +112,39 @@ gcloud firestore databases create --location=asia-south1 --project= - **Server-side:** Tokens are not stored on the server. They are generated on demand and returned immediately to the client. - **Client-side:** Short-lived SA tokens in `~/.config/extrasuite/token.json` -## Testing (Auth Flows) +## Testing -Use `extrasuite-client/examples/basic_usage.py` to validate the three main flows. Replace `` with your deployed server URL (e.g., `http://localhost:8001` for local development): +Due to tight integration with Google Cloud APIs, local testing is impractical. Developers should set up their own Google Cloud project following the same steps as Organization Setup (see `website/docs/deployment/`). -1. **First run (no cache):** token file missing, browser opens, user authenticates. - ```bash - rm -f ~/.config/extrasuite/token.json - PYTHONPATH=extrasuite-client/src python3 extrasuite-client/examples/basic_usage.py \ - --server https:// - ``` -2. **Cached token:** token file present and valid, no browser. - ```bash - PYTHONPATH=extrasuite-client/src python3 extrasuite-client/examples/basic_usage.py \ - --server https:// - ``` -3. **Session reuse (no cache, no re-auth):** delete token cache, browser opens, SSO/session skips login. +### Deploy from a branch + +To test changes before merging to main: + +1. Push your changes to a feature branch on GitHub +2. GitHub Actions automatically builds and pushes a container tagged with the branch name +3. Deploy the branch image to your Cloud Run instance: ```bash - rm -f ~/.config/extrasuite/token.json - PYTHONPATH=extrasuite-client/src python3 extrasuite-client/examples/basic_usage.py \ - --server https:// + gcloud run deploy extrasuite-server \ + --image asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server: \ + --region asia-southeast1 \ + --project ``` +### Validate auth flows + +Use `extrasuite-client/examples/basic_usage.py` to test the three main authentication scenarios: + +1. **First run (no cache):** Token file missing, browser opens, user authenticates +2. **Cached token:** Token file present and valid, no browser opens +3. **Session reuse:** Delete token cache, browser opens but SSO skips login + +```bash +# Clear cache and test fresh authentication +rm -f ~/.config/extrasuite/token.json +PYTHONPATH=extrasuite-client/src python3 extrasuite-client/examples/basic_usage.py \ + --server https:// +``` + ## Exception Handling The server uses centralized exception handling via FastAPI's `add_exception_handler`. Follow these principles: @@ -129,7 +169,7 @@ Docker images are automatically built and published to Google Artifact Registry **Automatic tagging:** | Trigger | Tags Created | |---------|--------------| -| Push to `main` | `main`, `sha-` | +| Push to any branch | ``, `sha-` | | Git tag `v*` | ``, `latest` | | Pull request | Build only (no push) | From ced8f69806fba3b2e86e76690637bd1306b06a66 Mon Sep 17 00:00:00 2001 From: Sripathi Krishnan Date: Fri, 23 Jan 2026 14:42:58 +0530 Subject: [PATCH 2/4] Rewrite deployment documentation for clarity and completeness - Restructure index.md as a clean overview with prerequisites checklist - Add both gcloud CLI and Google Cloud Console instructions for every step - Include direct links to Console pages and verification steps - Format troubleshooting with consistent Symptom/Cause/Solution pattern - Remove duplicate content between files Co-Authored-By: Claude Opus 4.5 --- website/docs/deployment/cloud-run.md | 460 ++++++++++++++++----- website/docs/deployment/iam-permissions.md | 188 +++++---- website/docs/deployment/index.md | 184 ++------- website/docs/deployment/operations.md | 257 +++++++----- 4 files changed, 658 insertions(+), 431 deletions(-) diff --git a/website/docs/deployment/cloud-run.md b/website/docs/deployment/cloud-run.md index 11915dd4..ae8faf51 100644 --- a/website/docs/deployment/cloud-run.md +++ b/website/docs/deployment/cloud-run.md @@ -1,80 +1,235 @@ -# Deploying to Cloud Run +# Step-by-Step Deployment Guide -This guide covers deploying the ExtraSuite server to Google Cloud Run. +This guide walks you through deploying ExtraSuite to Google Cloud Run. Each step includes both **gcloud CLI** commands and **Google Cloud Console** instructions. -## Prerequisites +--- -1. **Google Cloud Project** with billing enabled -2. **gcloud CLI** installed and configured +## Before You Begin -## Step 1: Enable Required APIs +**If using gcloud CLI:** Open a terminal and set your project ID. You'll use this variable throughout the guide. ```bash export PROJECT_ID=your-project-id +``` + +**If using Google Cloud Console:** Open [console.cloud.google.com](https://console.cloud.google.com) and select your project from the project dropdown at the top of the page. + +--- + +## Step 1: Enable Required APIs + +ExtraSuite needs several Google Cloud APIs to function. This step enables them. + +### Using gcloud CLI +```bash gcloud services enable \ run.googleapis.com \ firestore.googleapis.com \ iam.googleapis.com \ iamcredentials.googleapis.com \ secretmanager.googleapis.com \ + drive.googleapis.com \ + sheets.googleapis.com \ + docs.googleapis.com \ + slides.googleapis.com \ --project=$PROJECT_ID ``` +### Using Google Cloud Console + +1. Go to **APIs & Services > Library** ([direct link](https://console.cloud.google.com/apis/library)) +2. Search for and enable each of the following APIs: + - Cloud Run Admin API + - Cloud Firestore API + - Identity and Access Management (IAM) API + - IAM Service Account Credentials API + - Secret Manager API + - Google Drive API + - Google Sheets API + - Google Docs API + - Google Slides API + +**Verify:** After enabling, you should see all APIs listed in **APIs & Services > Enabled APIs**. + +--- + ## Step 2: Create Firestore Database +ExtraSuite uses Firestore to store user records and session data. Collections are created automatically when the server first runs. + +### Using gcloud CLI + ```bash -gcloud firestore databases create --location=asia-southeast1 --project=$PROJECT_ID +gcloud firestore databases create \ + --location=asia-southeast1 \ + --project=$PROJECT_ID ``` -Collections are created automatically on first use. +!!! note "Choose a location close to your users" + Replace `asia-southeast1` with your preferred [Firestore location](https://cloud.google.com/firestore/docs/locations). Common choices: `us-central1`, `europe-west1`, `asia-southeast1`. + +### Using Google Cloud Console + +1. Go to **Firestore** ([direct link](https://console.cloud.google.com/firestore)) +2. Click **Create Database** +3. Select **Native mode** (not Datastore mode) +4. Choose a location close to your users +5. Click **Create Database** + +**Verify:** The Firestore page should show "No collections yet" - this is expected. + +--- + +## Step 3: Configure OAuth Consent Screen + +Before creating OAuth credentials, you must configure the consent screen that users see when logging in. + +### Using Google Cloud Console + +1. Go to **APIs & Services > OAuth consent screen** ([direct link](https://console.cloud.google.com/apis/credentials/consent)) + +2. **Select User Type:** + - Choose **Internal** if all users are in your Google Workspace organization + - Choose **External** if users have personal Gmail accounts or are from multiple organizations + +3. Click **Create** + +4. **Fill in App Information:** + - **App name:** `ExtraSuite` (or your preferred name) + - **User support email:** Your email address + - **Developer contact email:** Your email address + +5. Click **Save and Continue** + +6. **Scopes:** Click **Save and Continue** (no additional scopes needed) + +7. **Test users (External only):** Add email addresses of users who can test before verification. Click **Save and Continue**. + +8. **Summary:** Review and click **Back to Dashboard** + +**Verify:** The OAuth consent screen page should show your app name with "Publishing status" displayed. + +--- + +## Step 4: Create OAuth Credentials + +Create the OAuth client ID and secret that ExtraSuite uses to authenticate users. + +### Using Google Cloud Console + +1. Go to **APIs & Services > Credentials** ([direct link](https://console.cloud.google.com/apis/credentials)) + +2. Click **Create Credentials > OAuth client ID** + +3. **Application type:** Select **Web application** + +4. **Name:** Enter `ExtraSuite Server` + +5. **Authorized redirect URIs:** Click **Add URI** and enter: + ``` + https://placeholder.example.com/api/auth/callback + ``` + (You'll update this with your actual URL after deployment in Step 7) -## Step 3: Create OAuth 2.0 Credentials +6. Click **Create** -1. Go to [Google Cloud Console > APIs & Services > Credentials](https://console.cloud.google.com/apis/credentials) -2. Click **Create Credentials** > **OAuth client ID** -3. Select **Web application** -4. Add authorized redirect URIs: - - `https://your-domain.com/api/auth/callback` - - `http://localhost:8001/api/auth/callback` (for development) -5. Save the **Client ID** and **Client Secret** +7. **Save your credentials:** A dialog shows your Client ID and Client Secret. Copy both values - you'll need them in Step 6. -## Step 4: Create Service Account for Cloud Run +!!! warning "Keep your Client Secret secure" + The Client Secret is like a password. Don't share it or commit it to version control. + +**Verify:** The Credentials page should list your new OAuth client under "OAuth 2.0 Client IDs". + +--- + +## Step 5: Create Service Account for ExtraSuite + +ExtraSuite needs a service account with permissions to create user service accounts and generate access tokens. + +### Using gcloud CLI + +**Create the service account:** ```bash -# Create service account gcloud iam service-accounts create extrasuite-server \ --display-name="ExtraSuite Server" \ --project=$PROJECT_ID +``` + +**Grant required permissions:** -# Grant Firestore access +```bash +# Permission to read/write Firestore gcloud projects add-iam-policy-binding $PROJECT_ID \ --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/datastore.user" -# Grant service account admin (for creating user SAs) +# Permission to create service accounts for users gcloud projects add-iam-policy-binding $PROJECT_ID \ --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/iam.serviceAccountAdmin" -# Grant token creator (for impersonation) +# Permission to generate access tokens gcloud projects add-iam-policy-binding $PROJECT_ID \ --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/iam.serviceAccountTokenCreator" ``` -## Step 5: Store Secrets in Secret Manager +### Using Google Cloud Console + +**Create the service account:** + +1. Go to **IAM & Admin > Service Accounts** ([direct link](https://console.cloud.google.com/iam-admin/serviceaccounts)) +2. Click **Create Service Account** +3. **Service account name:** `extrasuite-server` +4. **Service account ID:** Leave as auto-generated (`extrasuite-server`) +5. Click **Create and Continue** +6. Click **Done** (we'll add roles next) + +**Grant permissions:** + +1. Go to **IAM & Admin > IAM** ([direct link](https://console.cloud.google.com/iam-admin/iam)) +2. Click **Grant Access** +3. **New principals:** Enter `extrasuite-server@YOUR_PROJECT_ID.iam.gserviceaccount.com` +4. **Assign roles:** Add these three roles (click **Add Another Role** between each): + - `Cloud Datastore User` + - `Service Account Admin` + - `Service Account Token Creator` +5. Click **Save** + +**Verify:** In **IAM & Admin > IAM**, you should see `extrasuite-server@...` with three roles listed. + +--- + +## Step 6: Store Secrets in Secret Manager + +Store your OAuth credentials securely using Secret Manager. + +### Using gcloud CLI ```bash -# Create secrets (replace with your actual values) -echo -n "your-oauth-client-id" | gcloud secrets create extrasuite-client-id \ - --data-file=- --project=$PROJECT_ID -echo -n "your-oauth-client-secret" | gcloud secrets create extrasuite-client-secret \ - --data-file=- --project=$PROJECT_ID +# Store OAuth Client ID +echo -n "YOUR_CLIENT_ID" | gcloud secrets create extrasuite-client-id \ + --data-file=- \ + --project=$PROJECT_ID + +# Store OAuth Client Secret +echo -n "YOUR_CLIENT_SECRET" | gcloud secrets create extrasuite-client-secret \ + --data-file=- \ + --project=$PROJECT_ID + +# Generate and store a random secret key for session signing echo -n "$(openssl rand -base64 32)" | gcloud secrets create extrasuite-secret-key \ - --data-file=- --project=$PROJECT_ID + --data-file=- \ + --project=$PROJECT_ID +``` + +Replace `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` with the values from Step 4. + +**Grant the service account access to read these secrets:** -# Grant Cloud Run access to secrets +```bash for secret in extrasuite-client-id extrasuite-client-secret extrasuite-secret-key; do gcloud secrets add-iam-policy-binding $secret \ --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ @@ -83,7 +238,47 @@ for secret in extrasuite-client-id extrasuite-client-secret extrasuite-secret-ke done ``` -## Step 6: Deploy to Cloud Run +### Using Google Cloud Console + +**Create the secrets:** + +1. Go to **Security > Secret Manager** ([direct link](https://console.cloud.google.com/security/secret-manager)) + +2. Click **Create Secret** + - **Name:** `extrasuite-client-id` + - **Secret value:** Paste your OAuth Client ID from Step 4 + - Click **Create Secret** + +3. Click **Create Secret** again + - **Name:** `extrasuite-client-secret` + - **Secret value:** Paste your OAuth Client Secret from Step 4 + - Click **Create Secret** + +4. Click **Create Secret** again + - **Name:** `extrasuite-secret-key` + - **Secret value:** Generate a random string (you can use an online generator or run `openssl rand -base64 32` in a terminal) + - Click **Create Secret** + +**Grant access to each secret:** + +For each of the three secrets: + +1. Click the secret name to open it +2. Go to the **Permissions** tab +3. Click **Grant Access** +4. **New principals:** `extrasuite-server@YOUR_PROJECT_ID.iam.gserviceaccount.com` +5. **Role:** `Secret Manager Secret Accessor` +6. Click **Save** + +**Verify:** Each secret should show `extrasuite-server@...` in its Permissions tab. + +--- + +## Step 7: Deploy to Cloud Run + +Now deploy ExtraSuite using the pre-built Docker image. + +### Using gcloud CLI ```bash gcloud run deploy extrasuite-server \ @@ -92,29 +287,24 @@ gcloud run deploy extrasuite-server \ --region=asia-southeast1 \ --allow-unauthenticated \ --set-env-vars="GOOGLE_CLOUD_PROJECT=$PROJECT_ID" \ - --set-env-vars="BASE_DOMAIN=your-domain.com" \ + --set-env-vars="BASE_DOMAIN=placeholder.run.app" \ --set-secrets="SECRET_KEY=extrasuite-secret-key:latest" \ --set-secrets="GOOGLE_CLIENT_ID=extrasuite-client-id:latest" \ --set-secrets="GOOGLE_CLIENT_SECRET=extrasuite-client-secret:latest" \ --project=$PROJECT_ID ``` -## Step 7: Update OAuth Redirect URI - -Get your Cloud Run URL: +After deployment, get your service URL: ```bash SERVICE_URL=$(gcloud run services describe extrasuite-server \ --region=asia-southeast1 \ --project=$PROJECT_ID \ --format='value(status.url)') -echo $SERVICE_URL +echo "Your ExtraSuite URL: $SERVICE_URL" ``` -Update your OAuth credentials in Google Cloud Console to include: -`https://your-cloud-run-url/api/auth/callback` - -If not using a custom domain, update the BASE_DOMAIN: +**Update BASE_DOMAIN with your actual URL:** ```bash # Extract domain from URL (removes https://) @@ -126,108 +316,172 @@ gcloud run services update extrasuite-server \ --project=$PROJECT_ID ``` -## Step 8: Configure Email Domain Allowlist (Optional) +### Using Google Cloud Console + +1. Go to **Cloud Run** ([direct link](https://console.cloud.google.com/run)) + +2. Click **Create Service** + +3. **Container image:** Enter: + ``` + asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:latest + ``` + +4. **Service name:** `extrasuite-server` + +5. **Region:** Select a region (e.g., `asia-southeast1`) + +6. **Authentication:** Select **Allow unauthenticated invocations** + +7. Expand **Container(s), Volumes, Networking, Security** + +8. Click the **Container** tab, then **Variables & Secrets** + +9. **Add environment variables:** + - Click **Add Variable** + - Name: `GOOGLE_CLOUD_PROJECT`, Value: Your project ID + - Click **Add Variable** + - Name: `BASE_DOMAIN`, Value: `placeholder.run.app` (you'll update this after deployment) + +10. **Add secrets:** + - Click **Reference a Secret** + - Secret: `extrasuite-client-id`, Referenced as: Environment variable, Name: `GOOGLE_CLIENT_ID` + - Click **Reference a Secret** + - Secret: `extrasuite-client-secret`, Referenced as: Environment variable, Name: `GOOGLE_CLIENT_SECRET` + - Click **Reference a Secret** + - Secret: `extrasuite-secret-key`, Referenced as: Environment variable, Name: `SECRET_KEY` + +11. Click the **Security** tab + - **Service account:** Select `extrasuite-server@YOUR_PROJECT_ID.iam.gserviceaccount.com` + +12. Click **Create** + +13. After deployment completes, copy the URL shown (e.g., `https://extrasuite-server-xxxxx-as.a.run.app`) + +14. **Update BASE_DOMAIN:** + - Click **Edit & Deploy New Revision** + - Go to **Variables & Secrets** + - Update `BASE_DOMAIN` to your actual domain (without `https://`) + - Click **Deploy** + +--- + +## Step 8: Update OAuth Redirect URI + +Update your OAuth credentials with the actual Cloud Run URL. + +### Using Google Cloud Console -Restrict authentication to specific email domains: +1. Go to **APIs & Services > Credentials** ([direct link](https://console.cloud.google.com/apis/credentials)) + +2. Click your OAuth client ID (`ExtraSuite Server`) + +3. Under **Authorized redirect URIs:** + - Remove the placeholder URI + - Add your actual URI: `https://YOUR_CLOUD_RUN_URL/api/auth/callback` + +4. Click **Save** + +--- + +## Verify Your Deployment + +Test that everything is working: + +### Health Check ```bash -gcloud run services update extrasuite-server \ - --region=asia-southeast1 \ - --update-env-vars="ALLOWED_EMAIL_DOMAINS=example.com,company.org" \ - --project=$PROJECT_ID +curl https://YOUR_CLOUD_RUN_URL/api/health +``` + +Expected response: +```json +{"status":"healthy","service":"extrasuite-server"} ``` -## Step 9: Configure Domain Abbreviations (Optional) +### Login Test + +1. Open your Cloud Run URL in a browser +2. Click **Login with Google** +3. Complete the OAuth flow +4. You should see your service account email and the installation command + +**Congratulations!** ExtraSuite is now deployed. Share your Cloud Run URL with users in your organization. + +--- + +## Optional: Restrict Access by Email Domain + +Limit who can authenticate to specific email domains: -Service accounts are named using the user's email local part plus a domain abbreviation: +### Using gcloud CLI ```bash gcloud run services update extrasuite-server \ --region=asia-southeast1 \ - --update-env-vars='DOMAIN_ABBREVIATIONS={"example.com":"ex","company.org":"co"}' \ + --update-env-vars="ALLOWED_EMAIL_DOMAINS=yourcompany.com,partner.org" \ --project=$PROJECT_ID ``` -Example: `john@example.com` creates service account `john-ex@project.iam.gserviceaccount.com` +### Using Google Cloud Console -If a domain is not in the mapping, a 4-character hash is used as fallback. +1. Go to **Cloud Run > extrasuite-server > Edit & Deploy New Revision** +2. Under **Variables & Secrets**, add or update: + - Name: `ALLOWED_EMAIL_DOMAINS` + - Value: `yourcompany.com,partner.org` (comma-separated, no spaces) +3. Click **Deploy** -## Verification +--- -Test the deployment: +## Optional: Custom Domain -```bash -# Health check -curl $SERVICE_URL/api/health -# Expected: {"status":"healthy","service":"extrasuite-server"} -``` +Use your own domain instead of the auto-generated Cloud Run URL. -## Custom Domain - -To use a custom domain: +### Using gcloud CLI ```bash -# Create domain mapping gcloud beta run domain-mappings create \ --service=extrasuite-server \ --domain=extrasuite.yourdomain.com \ --region=asia-southeast1 \ --project=$PROJECT_ID - -# Configure DNS: Add CNAME record -# extrasuite.yourdomain.com -> ghs.googlehosted.com - -# Check status (SSL cert takes 15-30 minutes) -gcloud beta run domain-mappings describe \ - --domain=extrasuite.yourdomain.com \ - --region=asia-southeast1 \ - --project=$PROJECT_ID ``` -After the domain is configured, update the BASE_DOMAIN: +**Configure DNS:** Add a CNAME record pointing `extrasuite.yourdomain.com` to `ghs.googlehosted.com` + +**Wait for SSL certificate:** This can take 15-30 minutes. Check status: ```bash -gcloud run services update extrasuite-server \ +gcloud beta run domain-mappings describe \ + --domain=extrasuite.yourdomain.com \ --region=asia-southeast1 \ - --update-env-vars="BASE_DOMAIN=extrasuite.yourdomain.com" \ --project=$PROJECT_ID ``` -## Using a Specific Version +**Update BASE_DOMAIN and OAuth redirect URI** with your custom domain after SSL is provisioned. -Instead of `latest`, you can pin to a specific version: +### Using Google Cloud Console -```bash -# Use a specific release ---image=asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:v1.0.0 +1. Go to **Cloud Run > extrasuite-server** +2. Click the **Integrations** tab +3. Click **Add Integration > Custom domains** +4. Follow the prompts to add your domain and configure DNS -# Use a specific commit ---image=asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:sha-abc1234 +--- -# Use latest from main branch ---image=asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:main -``` +## Environment Variables Reference -## Production Recommendations - -1. **Enable Cloud Armor** for DDoS protection -2. **Set up Cloud Monitoring** alerts for errors -3. **Enable Cloud Audit Logs** for compliance -4. **Set minimum instances** to reduce cold starts: - ```bash - gcloud run services update extrasuite-server \ - --region=asia-southeast1 \ - --min-instances=1 \ - --project=$PROJECT_ID - ``` -5. **Configure Firestore TTL** for automatic cleanup of expired OAuth states: - ```bash - gcloud firestore fields ttls update expire_at \ - --collection-group=oauth_states \ - --enable-ttl \ - --project=$PROJECT_ID - ``` +| Variable | Required | Description | +|----------|----------|-------------| +| `GOOGLE_CLIENT_ID` | Yes | OAuth 2.0 Client ID | +| `GOOGLE_CLIENT_SECRET` | Yes | OAuth 2.0 Client Secret | +| `GOOGLE_CLOUD_PROJECT` | Yes | Your GCP project ID | +| `SECRET_KEY` | Yes | Random string for signing session tokens | +| `BASE_DOMAIN` | Yes | Your server's domain (without `https://`) | +| `ALLOWED_EMAIL_DOMAINS` | No | Comma-separated list of allowed email domains | +| `TOKEN_EXPIRY_MINUTES` | No | Access token lifetime (default: 60) | +| `SESSION_COOKIE_EXPIRY_MINUTES` | No | Session duration (default: 1440 = 24 hours) | --- -**Next:** Review [IAM Permissions](iam-permissions.md) for a complete permission reference. +**Next:** Review the [Operations Guide](operations.md) for monitoring and troubleshooting. diff --git a/website/docs/deployment/iam-permissions.md b/website/docs/deployment/iam-permissions.md index e8dca387..026977f4 100644 --- a/website/docs/deployment/iam-permissions.md +++ b/website/docs/deployment/iam-permissions.md @@ -1,148 +1,146 @@ # IAM Permissions Reference -This document lists all IAM permissions required by the ExtraSuite server. +This reference document explains the IAM roles and permissions required by ExtraSuite. -## Cloud Run Service Account +## Summary -The service account running the ExtraSuite server needs the following roles: +ExtraSuite requires **one service account** (`extrasuite-server`) with **four roles**: -### Firestore Access +| Role | Purpose | +|------|---------| +| `roles/datastore.user` | Read/write user records in Firestore | +| `roles/iam.serviceAccountAdmin` | Create service accounts for each user | +| `roles/iam.serviceAccountTokenCreator` | Generate short-lived access tokens | +| `roles/secretmanager.secretAccessor` | Read OAuth credentials and secret key | -**Role:** `roles/datastore.user` +## Role Details -**Purpose:** Read and write session data and user records in Firestore. +### Cloud Datastore User (`roles/datastore.user`) -```bash -gcloud projects add-iam-policy-binding $PROJECT_ID \ - --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ - --role="roles/datastore.user" -``` +**What it does:** Allows ExtraSuite to store and retrieve user records and session data in Firestore. -### Service Account Administration +**Scope:** Project-level binding -**Role:** `roles/iam.serviceAccountAdmin` +**Why needed:** When a user logs in, ExtraSuite stores their email and service account email in Firestore. This data persists across sessions. -**Purpose:** Create service accounts for users during first authentication. +--- -```bash -gcloud projects add-iam-policy-binding $PROJECT_ID \ - --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ - --role="roles/iam.serviceAccountAdmin" -``` +### Service Account Admin (`roles/iam.serviceAccountAdmin`) -### Token Creation (Impersonation) +**What it does:** Allows ExtraSuite to create, update, and delete service accounts. -**Role:** `roles/iam.serviceAccountTokenCreator` +**Scope:** Project-level binding -**Purpose:** Generate short-lived access tokens by impersonating user service accounts. +**Why needed:** ExtraSuite creates a dedicated service account for each user on first login (e.g., `john-ex@project.iam.gserviceaccount.com`). This role permits creating these accounts. -```bash -gcloud projects add-iam-policy-binding $PROJECT_ID \ - --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ - --role="roles/iam.serviceAccountTokenCreator" -``` +**Security note:** This is a powerful role. ExtraSuite only creates service accounts; it does not delete existing ones or modify their permissions. -### Secret Manager Access +--- -**Role:** `roles/secretmanager.secretAccessor` +### Service Account Token Creator (`roles/iam.serviceAccountTokenCreator`) -**Purpose:** Read secrets for OAuth credentials and signing keys. +**What it does:** Allows ExtraSuite to generate short-lived access tokens by impersonating user service accounts. -```bash -gcloud secrets add-iam-policy-binding $SECRET_NAME \ - --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ - --role="roles/secretmanager.secretAccessor" -``` +**Scope:** Project-level binding -## End-User Permissions +**Why needed:** When the CLI requests a token, ExtraSuite impersonates the user's service account to create a time-limited OAuth token. This is the core functionality that makes ExtraSuite work. -**Important:** End users do NOT need any GCP project access or IAM roles to use ExtraSuite. +**Security note:** Tokens are short-lived (default: 60 minutes) and scoped to Google Workspace APIs only. -### How It Works +--- -1. **User authenticates** via Google OAuth (only `openid` and `userinfo.email` scopes) -2. **Server creates a service account** for the user using its own credentials -3. **Server impersonates the SA** to generate a short-lived token -4. **Token is returned** to the CLI for use with Google Workspace APIs +### Secret Manager Secret Accessor (`roles/secretmanager.secretAccessor`) -The server acts as a trusted intermediary. Users only need to prove their identity - they don't need any GCP permissions. +**What it does:** Allows ExtraSuite to read specific secrets. -### What End Users Need +**Scope:** Secret-level binding (not project-level) -| Requirement | Needed? | Notes | -|-------------|---------|-------| -| GCP Project membership | **No** | IAM bindings work with any Google account | -| GCP Console access | **No** | Users never interact with GCP directly | -| Any project-level IAM roles | **No** | Permissions are per-SA, granted automatically | -| Google Workspace account | **Yes** | For OAuth authentication | +**Why needed:** ExtraSuite reads three secrets at startup: -### Organization Rollout +- `extrasuite-client-id` - OAuth Client ID +- `extrasuite-client-secret` - OAuth Client Secret +- `extrasuite-secret-key` - Session signing key -To enable all employees to use ExtraSuite: +**Security note:** This role is granted only on specific secrets, not all secrets in the project. -1. **Configure domain allowlist** on the server: - ```bash - ALLOWED_EMAIL_DOMAINS=example.com - ``` +--- -2. **Grant server permissions** (see sections above) +## End Users Do NOT Need GCP Access -3. **Share Google Workspace resources** with the created service accounts +A common misconception is that end users need GCP permissions. They do not. -That's it. No IAM configuration needed for individual users. +| Requirement | Needed? | +|-------------|---------| +| GCP project membership | No | +| GCP Console access | No | +| Any IAM roles | No | +| Google Workspace/Gmail account | Yes | -## OAuth Scopes +**How it works:** -### Server OAuth Scopes +1. User proves their identity via Google OAuth (only email scope) +2. ExtraSuite uses its own credentials to create the user's service account +3. ExtraSuite generates tokens using its own permissions +4. User receives tokens without ever touching GCP -The server uses Application Default Credentials with: +--- -- `https://www.googleapis.com/auth/cloud-platform` - For IAM and Firestore operations +## OAuth Scopes -### User OAuth Scopes (Authentication) +### User Authentication Scopes -Users are prompted to grant minimal scopes for identity verification only: +When users log in to ExtraSuite, they grant only: -- `openid` - OpenID Connect -- `https://www.googleapis.com/auth/userinfo.email` - Email address +- `openid` - Standard OpenID Connect +- `userinfo.email` - Access to email address -Users do NOT grant `cloud-platform` scope. The server uses its own credentials for all IAM operations. +Users do NOT grant `cloud-platform` or any Google Workspace scopes during login. ### Service Account Token Scopes -The short-lived tokens include: +The short-lived tokens issued to AI agents include: -- `https://www.googleapis.com/auth/spreadsheets` - Google Sheets read/write -- `https://www.googleapis.com/auth/documents` - Google Docs read/write -- `https://www.googleapis.com/auth/presentations` - Google Slides read/write -- `https://www.googleapis.com/auth/drive.readonly` - Google Drive read access +- `https://www.googleapis.com/auth/drive.readonly` - Read Google Drive files +- `https://www.googleapis.com/auth/spreadsheets` - Read/write Google Sheets +- `https://www.googleapis.com/auth/documents` - Read/write Google Docs +- `https://www.googleapis.com/auth/presentations` - Read/write Google Slides -## Summary Table +--- -### Runtime Service Account (`extrasuite-server`) +## Least Privilege Considerations -| Role | Resource | Purpose | -|------|----------|---------| -| `roles/datastore.user` | Project | Read/write Firestore | -| `roles/iam.serviceAccountAdmin` | Project | Create user SAs | -| `roles/iam.serviceAccountTokenCreator` | Project | Impersonate user SAs | -| `roles/secretmanager.secretAccessor` | Specific secrets | Read OAuth config | +The default setup grants project-level permissions for simplicity. For stricter security: -## Least Privilege Recommendations +### Option 1: Separate Projects -For production environments: +Use one project for ExtraSuite infrastructure and another for user service accounts. This isolates user service accounts from the ExtraSuite server. -1. **Use separate projects** for ExtraSuite infrastructure and user service accounts if needed for compliance +### Option 2: Conditional Token Creator -2. **Restrict token creator scope** to specific service accounts if possible: - ```bash - gcloud iam service-accounts add-iam-policy-binding \ - username-ex@$PROJECT_ID.iam.gserviceaccount.com \ - --member="serviceAccount:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" \ - --role="roles/iam.serviceAccountTokenCreator" - ``` +Instead of project-level `serviceAccountTokenCreator`, grant it only on specific service accounts: -3. **Audit regularly** using Cloud Audit Logs to monitor: - - Service account creation events - - Token generation events - - Failed authentication attempts +```bash +gcloud iam service-accounts add-iam-policy-binding \ + USERNAME-ABBR@PROJECT.iam.gserviceaccount.com \ + --member="serviceAccount:extrasuite-server@PROJECT.iam.gserviceaccount.com" \ + --role="roles/iam.serviceAccountTokenCreator" +``` + +This requires updating bindings each time a new user is onboarded. + +--- + +## Audit Logging + +Enable Cloud Audit Logs to monitor ExtraSuite activity: + +- **Admin Activity logs** (always on): Service account creation +- **Data Access logs** (must enable): Token generation, Firestore reads + +View logs in Cloud Logging: + +```bash +gcloud logging read 'protoPayload.serviceName="iam.googleapis.com"' \ + --project=$PROJECT_ID \ + --limit=20 +``` diff --git a/website/docs/deployment/index.md b/website/docs/deployment/index.md index 3fe500a6..a0e4800f 100644 --- a/website/docs/deployment/index.md +++ b/website/docs/deployment/index.md @@ -1,171 +1,73 @@ # Deployment Guide -ExtraSuite is designed to be deployed as a self-hosted service on Google Cloud Platform. This guide covers deploying your own instance. +ExtraSuite is designed to be deployed as a self-hosted service on Google Cloud Platform. This guide walks you through deploying your own instance. -## Quick Start +## What You're Building -```bash -# Set your project ID -export PROJECT_ID=your-project-id +When you complete this guide, you'll have: -# Deploy using the pre-built image -gcloud run deploy extrasuite-server \ - --image=asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:latest \ - --region=asia-southeast1 \ - --allow-unauthenticated \ - --project=$PROJECT_ID -``` - -See [Cloud Run Deployment](cloud-run.md) for complete setup instructions including OAuth configuration. - -## Architecture Overview +- A Cloud Run service that authenticates users and issues short-lived access tokens +- A Firestore database to store user records +- OAuth credentials so users can log in with their Google accounts +- A service account with permissions to create user-specific service accounts ``` ┌─────────────┐ ┌─────────────────┐ ┌──────────────────┐ -│ CLI Tool │────▶│ ExtraSuite │────▶│ Google Cloud │ +│ AI Agent │────▶│ ExtraSuite │────▶│ Google Cloud │ │ (Claude, │ │ Server │ │ - Firestore │ │ Codex) │◀────│ (Cloud Run) │◀────│ - IAM │ └─────────────┘ └─────────────────┘ │ - OAuth │ └──────────────────┘ ``` -## Prerequisites - -### Google Cloud Platform - -1. **GCP Project** with billing enabled -2. **gcloud CLI** installed and configured -3. **Required APIs**: - - Cloud Run - - Firestore - - IAM - - IAM Credentials - - Secret Manager - -### OAuth Credentials - -1. **Google OAuth Client** configured in Cloud Console -2. **OAuth consent screen** set up (internal or external) - -### Domain (Optional) - -- Custom domain with DNS access for production deployments - -## Docker Images - -Pre-built Docker images are available from Google Artifact Registry: - -``` -asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server -``` - -**Available tags:** - -| Tag | Description | -|-----|-------------| -| `latest` | Latest stable release | -| `v1.0.0` | Specific version | -| `main` | Latest from main branch | -| `sha-abc1234` | Specific commit | - -## Environment Variables +## Prerequisites Checklist -### Required +Before you begin, ensure you have: -| Variable | Description | -|----------|-------------| -| `GOOGLE_CLIENT_ID` | OAuth 2.0 Client ID | -| `GOOGLE_CLIENT_SECRET` | OAuth 2.0 Client Secret | -| `GOOGLE_CLOUD_PROJECT` | GCP Project ID for service accounts and Firestore | -| `SECRET_KEY` | Session signing key (use a long random string) | -| `BASE_DOMAIN` | Domain of your server (e.g., `extrasuite.example.com`) | +| Requirement | Details | +|-------------|---------| +| Google Cloud Project | A project where you have **Owner** or **Editor** role, with billing enabled | +| Google Workspace or Gmail | To test authentication after deployment | -### Optional +**Optional but recommended:** -| Variable | Description | Default | -|----------|-------------|---------| -| `SERVER_URL` | Full base URL for server | Derived from `BASE_DOMAIN` as `https://{BASE_DOMAIN}` | -| `FIRESTORE_DATABASE` | Firestore database name | `(default)` | -| `ALLOWED_EMAIL_DOMAINS` | Comma-separated allowed domains | All domains | -| `DOMAIN_ABBREVIATIONS` | JSON mapping for SA naming | Hash-based | +| Requirement | Details | +|-------------|---------| +| gcloud CLI | Makes deployment faster. [Install gcloud CLI](https://cloud.google.com/sdk/docs/install) | +| Custom domain | For a professional URL instead of the auto-generated Cloud Run URL | -### Session Cookie Settings +## Deployment Steps Overview -| Variable | Description | Default | -|----------|-------------|---------| -| `SESSION_COOKIE_NAME` | Cookie name | `session` | -| `SESSION_COOKIE_EXPIRY_MINUTES` | Session duration | `1440` (24 hours) | -| `SESSION_COOKIE_SAME_SITE` | SameSite policy | `lax` | -| `SESSION_COOKIE_HTTPS_ONLY` | HTTPS-only cookies | `true` | -| `SESSION_COOKIE_DOMAIN` | Cookie domain | Value of `BASE_DOMAIN` | +The deployment process has 8 steps: -### Token Settings +| Step | What You'll Do | Time | +|------|----------------|------| +| 1 | Enable Google Cloud APIs | 2 min | +| 2 | Create a Firestore database | 2 min | +| 3 | Configure OAuth consent screen | 5 min | +| 4 | Create OAuth credentials | 3 min | +| 5 | Create a service account for ExtraSuite | 3 min | +| 6 | Store secrets securely | 3 min | +| 7 | Deploy to Cloud Run | 5 min | +| 8 | Verify the deployment | 2 min | -| Variable | Description | Default | -|----------|-------------|---------| -| `TOKEN_EXPIRY_MINUTES` | Access token lifetime | `60` (1 hour) | +**[Start the Deployment Guide →](cloud-run.md)** -## Security Considerations +## Docker Image -### IAM Permissions +ExtraSuite publishes official Docker images to Google Artifact Registry: -The ExtraSuite server requires specific IAM roles. See [IAM Permissions](iam-permissions.md) for details. - -**Key principle:** End users do NOT need any GCP project access. The server acts as a trusted intermediary. - -### Secret Management - -- Store OAuth credentials in Secret Manager -- Generate strong random keys for session signing -- Rotate secrets periodically - -### Network Security - -- Always use HTTPS in production -- Consider Cloud Armor for DDoS protection -- Enable VPC Service Controls for sensitive environments - -## Deployment Guides - -- **[Cloud Run Deployment](cloud-run.md)** - Step-by-step deployment guide -- **[IAM Permissions](iam-permissions.md)** - Complete IAM role reference -- **[Operations](operations.md)** - Troubleshooting and common issues - -## Local Development - -For development and testing: - -```bash -cd extrasuite-server -cp .env.template .env -# Edit .env with your configuration - -uv sync -uv run uvicorn extrasuite_server.main:app --reload --port 8001 ``` - -Set `SERVER_URL=http://localhost:8001` for local development. - -## Building from Source - -If you prefer to build your own image: - -```bash -git clone https://github.com/think41/extrasuite.git -cd extrasuite - -# Build the image -docker build -t my-extrasuite-server:latest . - -# Push to your registry -docker tag my-extrasuite-server:latest gcr.io/$PROJECT_ID/extrasuite-server:latest -docker push gcr.io/$PROJECT_ID/extrasuite-server:latest +asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server ``` -## Support +| Tag | When to Use | +|-----|-------------| +| `latest` | Production deployments (latest stable release) | +| `v1.0.0` | Pin to a specific version | +| `main` | Testing latest changes (may be unstable) | -For deployment issues: +## Additional Resources -1. Check the [Operations guide](operations.md) for common issues -2. Review Cloud Run logs for errors -3. Open an issue on [GitHub](https://github.com/think41/extrasuite/issues) +- **[IAM Permissions Reference](iam-permissions.md)** - Detailed explanation of required permissions +- **[Operations Guide](operations.md)** - Monitoring, troubleshooting, and maintenance diff --git a/website/docs/deployment/operations.md b/website/docs/deployment/operations.md index 5e62c86d..ca3abff6 100644 --- a/website/docs/deployment/operations.md +++ b/website/docs/deployment/operations.md @@ -1,98 +1,152 @@ # Operations Guide -This document covers monitoring, debugging, and troubleshooting for ExtraSuite deployments. +This guide covers monitoring, updating, and troubleshooting your ExtraSuite deployment. ## Monitoring ### View Logs +#### Using gcloud CLI + ```bash -# Recent logs +# Recent logs (last 50 entries) gcloud run services logs read extrasuite-server \ --region=asia-southeast1 \ --project=$PROJECT_ID \ --limit=50 -# Error logs only +# Errors only gcloud logging read \ 'resource.type="cloud_run_revision" AND resource.labels.service_name="extrasuite-server" AND severity>=ERROR' \ --project=$PROJECT_ID \ - --limit=20 \ - --format="json" + --limit=20 ``` -### Health Checks +#### Using Google Cloud Console + +1. Go to **Cloud Run > extrasuite-server** +2. Click the **Logs** tab + +### Health Check ```bash -# Basic health check -curl https://your-domain.com/api/health -# Expected: {"status":"healthy","service":"extrasuite-server"} +curl https://YOUR_DOMAIN/api/health +``` + +Expected response: +```json +{"status":"healthy","service":"extrasuite-server"} ``` ### List User Service Accounts +See how many users have been onboarded: + ```bash gcloud iam service-accounts list --project=$PROJECT_ID \ --format="table(email,displayName)" ``` -### Check IAM Permissions +--- + +## Updating ExtraSuite + +### Update to Latest Version + +#### Using gcloud CLI ```bash -gcloud projects get-iam-policy $PROJECT_ID \ - --flatten="bindings[].members" \ - --format="table(bindings.role,bindings.members)" \ - --filter="bindings.members:extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com" +gcloud run services update extrasuite-server \ + --region=asia-southeast1 \ + --image=asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:latest \ + --project=$PROJECT_ID ``` -## Common Issues +#### Using Google Cloud Console -### OAuth Scopes Showing Too Many Permissions +1. Go to **Cloud Run > extrasuite-server** +2. Click **Edit & Deploy New Revision** +3. Update the container image URL to use `latest` or a specific version +4. Click **Deploy** -**Symptom:** OAuth consent screen asks for "See, edit, configure and delete your Google Cloud data" instead of just email access. +### Pin to a Specific Version -**Cause:** Using `include_granted_scopes="true"` causes Google to include any previously granted scopes. +For production stability, pin to a version tag: -**Solution:** Users may need to revoke existing app permissions at [myaccount.google.com/permissions](https://myaccount.google.com/permissions) before re-authenticating. +```bash +gcloud run services update extrasuite-server \ + --region=asia-southeast1 \ + --image=asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:v1.0.0 \ + --project=$PROJECT_ID +``` -### Firestore "No document to update" Error +### Rollback to Previous Version -**Symptom:** +```bash +# List available revisions +gcloud run revisions list \ + --service=extrasuite-server \ + --region=asia-southeast1 \ + --project=$PROJECT_ID + +# Route all traffic to a previous revision +gcloud run services update-traffic extrasuite-server \ + --region=asia-southeast1 \ + --to-revisions=extrasuite-server-00001-abc=100 \ + --project=$PROJECT_ID +``` + +--- + +## Troubleshooting + +### OAuth Consent Shows Too Many Permissions + +**Symptom:** Users see "See, edit, configure and delete your Google Cloud data" instead of just email access. + +**Cause:** Google includes previously granted scopes when `include_granted_scopes` is enabled. + +**Solution:** Users should revoke ExtraSuite's access at [myaccount.google.com/permissions](https://myaccount.google.com/permissions), then log in again. + +--- + +### 404 Error: "No document to update" + +**Symptom:** Error in logs: ``` NotFound: 404 No document to update: projects/.../documents/users/email@domain.com ``` -**Cause:** Code is using Firestore `update()` method on a document that doesn't exist. +**Cause:** This is a code bug where `update()` is used instead of `set(merge=True)`. -**Solution:** Use `set(merge=True)` instead of `update()` for upsert behavior. +**Solution:** Update to the latest version of ExtraSuite which fixes this issue. + +--- ### Email Domain Allowlist Not Working -**Symptom:** Valid email domains are rejected. +**Symptom:** Valid email domains are rejected during login. -**Cause:** The gcloud CLI may interpret commas incorrectly in some shells. +**Cause:** The `ALLOWED_EMAIL_DOMAINS` environment variable may be malformed. -**Solution:** Verify the environment variable is set correctly: +**Diagnosis:** ```bash gcloud run services describe extrasuite-server \ --region=asia-southeast1 \ - --format="value(spec.template.spec.containers[0].env)" + --project=$PROJECT_ID \ + --format="yaml(spec.template.spec.containers[0].env)" ``` -### Firestore Database Recreation Delay - -**Symptom:** After deleting a Firestore database, recreating fails: +**Solution:** Ensure domains are comma-separated with no spaces: ``` -FAILED_PRECONDITION: Database ID '(default)' is not available. Please retry in X seconds. +ALLOWED_EMAIL_DOMAINS=example.com,company.org ``` -**Cause:** Firestore requires a cooldown period (~4-5 minutes) after database deletion. - -**Solution:** Wait for the cooldown period before recreating. +--- -### Custom Domain SSL Certificate Pending +### Custom Domain SSL Certificate Not Working -**Symptom:** Custom domain doesn't work with HTTPS. +**Symptom:** HTTPS doesn't work on your custom domain. **Cause:** Google-managed SSL certificates take 15-30 minutes to provision. @@ -101,14 +155,17 @@ FAILED_PRECONDITION: Database ID '(default)' is not available. Please retry in X gcloud beta run domain-mappings describe \ --domain=your-domain.com \ --region=asia-southeast1 \ + --project=$PROJECT_ID \ --format="yaml(status.conditions)" -# Verify DNS +# Verify DNS is configured dig your-domain.com CNAME +short -# Should return: ghs.googlehosted.com. +# Expected: ghs.googlehosted.com. ``` -**Solution:** Wait for certificate provisioning. When `CertificateProvisioned` shows `status: 'True'`, HTTPS will work. +**Solution:** Wait for certificate provisioning. Once `CertificateProvisioned` shows `status: 'True'`, HTTPS will work. + +--- ### IAM Policy Binding Fails @@ -127,106 +184,122 @@ gcloud projects add-iam-policy-binding $PROJECT_ID \ --condition=None ``` -## Updating the Deployment +--- -### Update to Latest Version +### Firestore Database Recreation Fails -```bash -gcloud run services update extrasuite-server \ - --region=asia-southeast1 \ - --image=asia-southeast1-docker.pkg.dev/thinker41/extrasuite/server:latest \ - --project=$PROJECT_ID +**Symptom:** After deleting a Firestore database, recreating fails with: +``` +FAILED_PRECONDITION: Database ID '(default)' is not available. Please retry in X seconds. ``` -### Update Environment Variables +**Cause:** Firestore requires a cooldown period (~5 minutes) after database deletion. -```bash -gcloud run services update extrasuite-server \ - --region=asia-southeast1 \ - --update-env-vars="KEY=value" \ - --project=$PROJECT_ID -``` +**Solution:** Wait 5 minutes, then try again. -### Rollback to Previous Version +--- -```bash -# List revisions -gcloud run revisions list \ - --service=extrasuite-server \ - --region=asia-southeast1 \ - --project=$PROJECT_ID +### Service Account Quota Exceeded -# Route traffic to a specific revision -gcloud run services update-traffic extrasuite-server \ - --region=asia-southeast1 \ - --to-revisions=extrasuite-server-00001-abc=100 \ - --project=$PROJECT_ID -``` +**Symptom:** New users cannot onboard. -## Testing Authentication Flow +**Cause:** GCP default quota is 100 service accounts per project. +**Diagnosis:** ```bash -# Clear token cache -rm -f ~/.config/extrasuite/token.json - -# Run test -cd /path/to/extrasuite -PYTHONPATH=extrasuite-client/src python3 extrasuite-client/examples/basic_usage.py \ - --server https://your-domain.com +gcloud iam service-accounts list --project=$PROJECT_ID | wc -l ``` -**Three test scenarios:** +**Solution:** [Request a quota increase](https://console.cloud.google.com/iam-admin/quotas) for "Service Accounts per Project". -1. **First run (no cache):** Token file missing, browser opens, user authenticates -2. **Cached token:** Token file present and valid, no browser needed -3. **Session reuse:** Delete token cache, browser opens, SSO/session skips login prompt +--- ## Secrets Management -### Rotate Secrets +### Rotate a Secret ```bash -# Update secret with new value -echo -n "new-value" | gcloud secrets versions add extrasuite-secret-key --data-file=- +# Add new version +echo -n "new-secret-value" | gcloud secrets versions add extrasuite-secret-key \ + --data-file=- \ + --project=$PROJECT_ID -# Deploy with new version (Cloud Run auto-updates on next deploy) +# Redeploy to pick up new version gcloud run services update extrasuite-server \ --region=asia-southeast1 \ --update-secrets="SECRET_KEY=extrasuite-secret-key:latest" \ --project=$PROJECT_ID ``` -## Service Account Quota +### View Secret Versions + +```bash +gcloud secrets versions list extrasuite-secret-key --project=$PROJECT_ID +``` + +--- -ExtraSuite creates one service account per user. Monitor usage: +## Testing the Authentication Flow + +Use the included test script to verify authentication works: ```bash -# Count service accounts -gcloud iam service-accounts list --project=$PROJECT_ID | wc -l +# Clear any cached token +rm -f ~/.config/extrasuite/token.json + +# Run the test +cd /path/to/extrasuite +PYTHONPATH=extrasuite-client/src python3 extrasuite-client/examples/basic_usage.py \ + --server https://YOUR_DOMAIN ``` -GCP default quota is 100 service accounts per project. [Request an increase](https://console.cloud.google.com/iam-admin/quotas) if needed. +**Expected behavior:** -## Clean Up +1. Browser opens to the login page +2. User authenticates with Google +3. Browser redirects back and closes +4. Script prints a valid access token -To remove all ExtraSuite resources: +--- + +## Cleanup + +To completely remove ExtraSuite from your project: ```bash # Delete Cloud Run service gcloud run services delete extrasuite-server \ --region=asia-southeast1 \ - --project=$PROJECT_ID + --project=$PROJECT_ID \ + --quiet -# Delete user service accounts (adjust pattern for your domains) +# Delete user service accounts (adjust pattern for your domain abbreviations) gcloud iam service-accounts list --project=$PROJECT_ID --format="value(email)" | \ grep -E '-(ex|co)@' | \ xargs -I {} gcloud iam service-accounts delete {} --project=$PROJECT_ID --quiet +# Delete the server service account +gcloud iam service-accounts delete extrasuite-server@$PROJECT_ID.iam.gserviceaccount.com \ + --project=$PROJECT_ID --quiet + # Delete Firestore database -gcloud firestore databases delete --database="(default)" --project=$PROJECT_ID +gcloud firestore databases delete --database="(default)" --project=$PROJECT_ID --quiet # Delete secrets for secret in extrasuite-client-id extrasuite-client-secret extrasuite-secret-key; do gcloud secrets delete $secret --project=$PROJECT_ID --quiet done ``` + +--- + +## Getting Help + +If you encounter issues not covered here: + +1. Check [Cloud Run logs](#view-logs) for error details +2. Search [GitHub Issues](https://github.com/think41/extrasuite/issues) +3. Open a new issue with: + - Error message from logs + - Steps to reproduce + - Your configuration (without secrets) From 2fb1ccd9610d418b55c28592604498bafb559d50 Mon Sep 17 00:00:00 2001 From: Sripathi Krishnan Date: Fri, 23 Jan 2026 15:00:09 +0530 Subject: [PATCH 3/4] Clarify serviceAccountAdmin role is safe despite being powerful Explain that creating service accounts is separate from granting permissions. The role cannot assign IAM roles to created accounts, so they have zero access to GCP resources. Co-Authored-By: Claude Opus 4.5 --- website/docs/deployment/iam-permissions.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/website/docs/deployment/iam-permissions.md b/website/docs/deployment/iam-permissions.md index 026977f4..97dc32fc 100644 --- a/website/docs/deployment/iam-permissions.md +++ b/website/docs/deployment/iam-permissions.md @@ -33,7 +33,19 @@ ExtraSuite requires **one service account** (`extrasuite-server`) with **four ro **Why needed:** ExtraSuite creates a dedicated service account for each user on first login (e.g., `john-ex@project.iam.gserviceaccount.com`). This role permits creating these accounts. -**Security note:** This is a powerful role. ExtraSuite only creates service accounts; it does not delete existing ones or modify their permissions. +**Security note:** While this sounds like a powerful role, it is safe because **creating a service account is separate from granting it permissions**. + +`serviceAccountAdmin` allows: + +- ✓ Creating service accounts +- ✓ Updating metadata (display name, description) +- ✓ Deleting service accounts +- ✗ Granting IAM roles to service accounts +- ✗ Modifying project IAM policies + +To grant any IAM role to a service account, you need `roles/resourcemanager.projectIamAdmin` or specific `setIamPolicy` permissions—which ExtraSuite does not have. + +**The service accounts ExtraSuite creates have zero IAM roles.** They cannot access any GCP resources (BigQuery, Cloud Storage, Compute, etc.). They can only access Google Workspace files (Sheets, Docs, Slides) that users explicitly share with them. --- From e0f430d29f178c5350c5ed26fb08c0c1c7c608a9 Mon Sep 17 00:00:00 2001 From: aamnasiddiqui-think41 Date: Fri, 23 Jan 2026 15:12:08 +0530 Subject: [PATCH 4/4] How it works section modified, added 3 sreps --- website/docs/index.html | 183 +++++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 78 deletions(-) diff --git a/website/docs/index.html b/website/docs/index.html index d75334ae..3962330c 100644 --- a/website/docs/index.html +++ b/website/docs/index.html @@ -898,94 +898,121 @@

- -
+ +