feat: GWS native provider with gateway-managed OAuth refresh#44
Merged
Conversation
Replace GWS file staging with OpenShell provider system. The gateway stores client_id, client_secret, and refresh_token as provider credentials (secrets marked non-injectable), refreshes the access token automatically every ~55 minutes, and injects GOOGLE_WORKSPACE_CLI_TOKEN as a proxy-resolved placeholder into sandboxes. The L7 proxy resolves the placeholder in Authorization headers -- the sandbox never sees the real access token, refresh token, or client secret. Eliminates: stageGWSCreds, openshell-gws K8s Secret, launcher /secrets/gws volume mount, and GOOGLE_WORKSPACE_CLI_* env vars from profiles. Adds ProviderRefreshConfigure and ProviderRefreshRotate to the Gateway interface and CLI wrapper. Validated locally: GOOGLE_WORKSPACE_CLI_TOKEN is a proxy placeholder, Gmail API calls succeed, forced token rotation leaves the sandbox working. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ploy Implement kind cluster deployment (NodePort + HTTP, disableTls=true) and add validate-local/validate-kind Makefile targets that run unit tests, bats, and full integration including a GWS token lifecycle test. Kind deploy flow: - NodePort service with plaintext HTTP (disableTls: true, pkiInitJob still runs for JWT signing keys) - Node IP resolved via kubectl get nodes after Helm rollout completes - http:// URL skips mTLS and browser auth automatically - No cert extraction needed (direct mode, allowUnauthenticatedUsers: true) - All four providers (github, vertex-local, atlassian, gws) enabled GWS verification in sandbox_verify(): - Checks GOOGLE_WORKSPACE_CLI_TOKEN is a proxy placeholder (not real token) - Makes a live Gmail API call through the proxy to confirm resolution works - test_gws() adds forced rotation + post-rotation API call k8s.Runner interface: GetServiceNodePort and GetNodeInternalIP added. Gateway.GatewayAdd: insecure bool parameter added (currently unused, kept for future https+insecure path). Validated: harness deploy kind, harness providers, kind sandbox with gws provider all work end-to-end on macOS Docker Desktop kind cluster. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The refresh.scopes field in the provider profile YAML is not passed to Google during token refresh — only --material scopes=... is. Move scope enforcement into registerGWS() as a scopes material key so the gateway mints a narrowed access token (gmail.readonly, calendar.readonly, drive.readonly, tasks only). Verified: removing gmail from scopes causes 403 PERMISSION_DENIED on Gmail API calls; calendar with scope retained works fine. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Switch tasks scope from read-write to tasks.readonly (read-only variant). GWS scopes are now fully read-only: gmail.readonly, calendar.readonly, drive.readonly, tasks.readonly. Remove curl from gws profile binaries — it was only there for scope enforcement testing. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Move scopes out of providers.go and into sandbox/profiles/gws.yaml under the refresh section — single source of truth for GWS config. registerGWS() reads refresh.scopes from the profile via gwsProfileScopes() and passes them as material to provider refresh configure. Falls back to no scope restriction if the profile is missing or unparseable. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…inaries tasks.readonly is not a scope the gws refresh token was granted (only tasks read-write). Fix gws.yaml to use the correct tasks scope. Restore curl to the profile binaries list so the proxy allows it for direct API calls and test-flow.sh sandbox verification. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Runs harness deploy kind + test-flow.sh kind --full --no-providers --profile=ci on ubuntu-latest. Uses the public community base image, no credentials needed. Installs kind and helm via the official scripts. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace manual curl install (which fails due to /usr/local/bin permissions) with helm/kind-action@v1 and azure/setup-helm@v4 which handle installation correctly on GitHub Actions runners. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Add Validation section to AGENTS.md: - No-credential flows (CI/kind): what runs in GitHub Actions - Full flows (local dev with personal creds): what requires real credentials - Table showing what each tier covers - Future work note on service accounts for GHA Update workaround table: GWS credential export/upload is resolved (now a native provider with gateway-managed OAuth refresh). Mark as resolved. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
default mode: expects user credentials, tests full provider chain ci mode: no credentials, tests gateway deploy + sandbox lifecycle --ci flag on test-flow.sh sets --no-providers --profile=ci --full. Makefile adds validate-local-ci and validate-kind-ci targets. integration.yml local and kind jobs use --ci flag. AGENTS.md documents default vs ci as the primary dimension. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This was referenced Jun 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
What's removed
stageGWSCreds()-- no more exporting and uploading credential filesopenshell-gwsK8s Secret and launcher/secrets/gwsvolume mountGOOGLE_WORKSPACE_CLI_CONFIG_DIR/GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILEenv vars from profilesgwsProvider boolparameter threading throughStageHarnessDirandensureCredsWhat's added
sandbox/profiles/gws.yaml-- custom provider profile with oauth2_refresh_token strategy and endpoint allowlistProviderRefreshConfigureandProviderRefreshRotateon the Gateway interface and CLI wrapperregisterGWS()in providers.go registers the provider with a placeholder token, configures gateway-managed refresh with secret material, and forces an immediate rotationTest plan
CGO_ENABLED=0 go test ./...)harness providersregisters gws with "gateway-managed token refresh"GOOGLE_WORKSPACE_CLI_TOKEN=openshell:resolve:env:v..._GOOGLE_WORKSPACE_CLI_TOKEN(placeholder, not real token)openshell provider refresh rotate gws --credential-key GOOGLE_WORKSPACE_CLI_TOKENsucceeds and sandbox continues workingAll validated locally on this branch.
🤖 Generated with Claude Code