diff --git a/.github/workflows/cloud-deploy.yml b/.github/workflows/cloud-deploy.yml index 365ea905c..a99800c7e 100644 --- a/.github/workflows/cloud-deploy.yml +++ b/.github/workflows/cloud-deploy.yml @@ -219,6 +219,14 @@ jobs: alera-google-oauth-client-secret alera-tombstone-pepper ) + # A web client id mounts its secret on the Cloud Run revision, so that + # secret must have a version before the image build starts. + for provider in google github; do + if grep -Eq "^web_${provider}_oauth_client_id[[:space:]]*=[[:space:]]*\"[^\"]+\"" \ + infra/production/production.auto.tfvars; then + required_secrets+=("alera-web-${provider}-oauth-client-secret") + fi + done for secret in "${required_secrets[@]}"; do enabled_version="$( gcloud secrets versions list "$secret" \ diff --git a/docs/cloud-operations.md b/docs/cloud-operations.md index 5c82d2667..4487e0ca2 100644 --- a/docs/cloud-operations.md +++ b/docs/cloud-operations.md @@ -104,6 +104,7 @@ gcloud secrets versions add alera-edge-origin-token --data-file=- gcloud secrets versions add alera-github-oauth-client-secret --data-file=- gcloud secrets versions add alera-google-oauth-client-secret --data-file=- gcloud secrets versions add alera-tombstone-pepper --data-file=- +gcloud secrets versions add alera-web-google-oauth-client-secret --data-file=- ``` Generate independent high-entropy values for the edge token and tombstone pepper. Do not reuse an OAuth client secret or copy local development values. diff --git a/docs/cloud-setup.md b/docs/cloud-setup.md index abebb4a00..ad1f951f7 100644 --- a/docs/cloud-setup.md +++ b/docs/cloud-setup.md @@ -160,6 +160,14 @@ Record: - Client id: public configuration in `terraform.tfvars` - Client secret: secret value added to `alera-google-oauth-client-secret` +MCP and device sign-in run in a browser and return to `https://api.alera.build/oauth/callback`, which a desktop client cannot accept. Create a second OAuth client for them: + +- Application type: Web application +- Name: `Alera Web` +- Authorized redirect URI: `https://api.alera.build/oauth/callback` + +Record its client id as `web_google_oauth_client_id` and add its secret to `alera-web-google-oauth-client-secret` before the id is applied. + The backend verifies the Google ID token, including signature, issuer, audience, authorized presenter, expiry, and nonce. Provider tokens are discarded after identity resolution. ## GitHub OAuth Registration @@ -171,9 +179,10 @@ Configuration: - Application name: `Alera` - Homepage URL: `https://alera.build` - Authorization callback URL: `http://127.0.0.1/callback` +- Second redirect URI: `https://api.alera.build/oauth/callback` - Device Flow: disabled -The runtime supplies the actual loopback port. GitHub permits a loopback redirect to vary the port while preserving the registered host and path. +The runtime supplies the actual loopback port. GitHub permits a loopback redirect to vary the port while preserving the registered host and path. The second URI serves MCP and device sign-in, so production reuses this app for them and leaves `web_github_oauth_client_id` empty. The application requests only: @@ -293,7 +302,7 @@ The value is public signing-key material and may appear in OpenTofu state. The p ## Secret Manager Values -OpenTofu creates six secret containers. Five require initial values: +OpenTofu creates eight secret containers. Five require initial values, and a web OAuth secret is required once its client id is set: | Secret | Value | | --- | --- | @@ -303,6 +312,8 @@ OpenTofu creates six secret containers. Five require initial values: | `alera-google-oauth-client-secret` | Google desktop OAuth client secret | | `alera-tombstone-pepper` | Independent random value with at least 32 characters | | `alera-edge-previous-origin-token` | Leave without a version until an edge-token rotation | +| `alera-web-google-oauth-client-secret` | Google web OAuth client secret, required while `web_google_oauth_client_id` is set | +| `alera-web-github-oauth-client-secret` | Leave without a version while `web_github_oauth_client_id` is empty | Add values through standard input: @@ -312,6 +323,7 @@ gcloud secrets versions add alera-edge-origin-token --data-file=- gcloud secrets versions add alera-github-oauth-client-secret --data-file=- gcloud secrets versions add alera-google-oauth-client-secret --data-file=- gcloud secrets versions add alera-tombstone-pepper --data-file=- +gcloud secrets versions add alera-web-google-oauth-client-secret --data-file=- ``` Use a password manager or secure random generator for the origin token and tombstone pepper. They must be unrelated values and must not reuse either OAuth secret. diff --git a/docs/remote-mcp.md b/docs/remote-mcp.md index 9551af433..28348b580 100644 --- a/docs/remote-mcp.md +++ b/docs/remote-mcp.md @@ -68,7 +68,7 @@ Rules: - OAuth endpoints use the standard snake_case field names and `{ error, error_description }` errors; every other route keeps camelCase and `{ error: { code, message } }`. - Unknown scopes such as `offline_access` are ignored, and `mcp:read` is always granted. A token request may omit `redirect_uri`; when present it must match. - `ALERA_MCP_ENABLED=false` removes the metadata, registration, authorize, token, revoke, and gateway routes. Device sign-in, grant listing, and runtime naming keep working. -- The web login uses `{ALERA_PUBLIC_BASE_URL}/oauth/callback`. Production needs web OAuth clients for Google and GitHub that admit that redirect: `ALERA_WEB_GOOGLE_CLIENT_ID`, `ALERA_WEB_GOOGLE_CLIENT_SECRET`, `ALERA_WEB_GITHUB_CLIENT_ID`, and `ALERA_WEB_GITHUB_CLIENT_SECRET`. Without them the native client credentials are reused. +- The web login uses `{ALERA_PUBLIC_BASE_URL}/oauth/callback`. Each provider needs a client that admits that redirect. Google desktop clients only accept loopback redirects, so production sets a separate Google web client (`ALERA_WEB_GOOGLE_CLIENT_ID` from `web_google_oauth_client_id`, `ALERA_WEB_GOOGLE_CLIENT_SECRET` from the `alera-web-google-oauth-client-secret` secret). A GitHub OAuth App accepts several redirect URIs, so production reuses the desktop app with that callback added and leaves `ALERA_WEB_GITHUB_CLIENT_ID` and `ALERA_WEB_GITHUB_CLIENT_SECRET` unset. Without web credentials a provider reuses the native client. ### Device Authorization diff --git a/infra/production/production.auto.tfvars b/infra/production/production.auto.tfvars index 09c36c545..4bf08541b 100644 --- a/infra/production/production.auto.tfvars +++ b/infra/production/production.auto.tfvars @@ -1,11 +1,12 @@ -gcp_project_id = "alera-production" -gcp_region = "us-central1" -cloudflare_zone_id = "d5bb590bba0a461c5a5f699b40b2c95f" -api_hostname = "api.alera.build" -google_oauth_client_id = "850463913236-eun5inavt29h0cpanc3utkvpdu4tari5.apps.googleusercontent.com" -github_oauth_client_id = "Ov23lihNhXgxBYUsBk3N" -neon_project_id = "empty-glade-74978232" -signing_key_id = "alera-production-v1" -kms_key_version = "1" -kms_public_key_b64url = "4OHKJMPgzVh_4XSvY11WVLqTSNPG_opDR53rPMp-3y4" -previous_jwks_json = "{\"keys\":[]}" +gcp_project_id = "alera-production" +gcp_region = "us-central1" +cloudflare_zone_id = "d5bb590bba0a461c5a5f699b40b2c95f" +api_hostname = "api.alera.build" +google_oauth_client_id = "850463913236-eun5inavt29h0cpanc3utkvpdu4tari5.apps.googleusercontent.com" +github_oauth_client_id = "Ov23lihNhXgxBYUsBk3N" +web_google_oauth_client_id = "850463913236-s6laqmn715qj97rjjjp863epsn87325d.apps.googleusercontent.com" +neon_project_id = "empty-glade-74978232" +signing_key_id = "alera-production-v1" +kms_key_version = "1" +kms_public_key_b64url = "4OHKJMPgzVh_4XSvY11WVLqTSNPG_opDR53rPMp-3y4" +previous_jwks_json = "{\"keys\":[]}" diff --git a/infra/production/readme.md b/infra/production/readme.md index a82b5b6a9..2d3acdd9e 100644 --- a/infra/production/readme.md +++ b/infra/production/readme.md @@ -47,6 +47,7 @@ gcloud secrets versions add alera-edge-origin-token --data-file=- gcloud secrets versions add alera-github-oauth-client-secret --data-file=- gcloud secrets versions add alera-google-oauth-client-secret --data-file=- gcloud secrets versions add alera-tombstone-pepper --data-file=- +gcloud secrets versions add alera-web-google-oauth-client-secret --data-file=- ``` Use independent random values for the origin token and tombstone pepper. Never reuse an OAuth client secret.