Skip to content

feat: GWS native provider with gateway-managed OAuth refresh#44

Merged
robbycochran merged 11 commits into
mainfrom
rc-simplify-gain
Jun 7, 2026
Merged

feat: GWS native provider with gateway-managed OAuth refresh#44
robbycochran merged 11 commits into
mainfrom
rc-simplify-gain

Conversation

@robbycochran

Copy link
Copy Markdown
Collaborator

Summary

  • Replaces GWS file staging (credentials.json + client_secret.json upload) with OpenShell's native provider system and gateway-managed OAuth token refresh
  • The gateway stores client_id, client_secret, and refresh_token as provider credentials with secret material keys -- they are never injected into sandboxes
  • GOOGLE_WORKSPACE_CLI_TOKEN is injected as a proxy-resolved placeholder; the L7 proxy resolves it to the real access token in Authorization headers
  • Gateway refreshes the token automatically every ~55 minutes via the oauth2-refresh-token strategy

What's removed

  • stageGWSCreds() -- no more exporting and uploading credential files
  • openshell-gws K8s Secret and launcher /secrets/gws volume mount
  • GOOGLE_WORKSPACE_CLI_CONFIG_DIR / GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE env vars from profiles
  • gwsProvider bool parameter threading through StageHarnessDir and ensureCreds

What's added

  • sandbox/profiles/gws.yaml -- custom provider profile with oauth2_refresh_token strategy and endpoint allowlist
  • ProviderRefreshConfigure and ProviderRefreshRotate on the Gateway interface and CLI wrapper
  • registerGWS() in providers.go registers the provider with a placeholder token, configures gateway-managed refresh with secret material, and forces an immediate rotation

Test plan

  • All unit tests pass (CGO_ENABLED=0 go test ./...)
  • harness providers registers gws with "gateway-managed token refresh"
  • Sandbox shows GOOGLE_WORKSPACE_CLI_TOKEN=openshell:resolve:env:v..._GOOGLE_WORKSPACE_CLI_TOKEN (placeholder, not real token)
  • Gmail API call through proxy returns real data
  • openshell provider refresh rotate gws --credential-key GOOGLE_WORKSPACE_CLI_TOKEN succeeds and sandbox continues working

All validated locally on this branch.

🤖 Generated with Claude Code

robbycochran and others added 11 commits June 5, 2026 20:48
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>
@robbycochran robbycochran merged commit 6dcddb3 into main Jun 7, 2026
6 checks passed
@robbycochran robbycochran deleted the rc-simplify-gain branch June 13, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant