Skip to content

feat/google-service-account - #416

Open
yosukekmt wants to merge 1 commit into
onecli:mainfrom
yosukekmt:feat/google-service-account
Open

feat/google-service-account#416
yosukekmt wants to merge 1 commit into
onecli:mainfrom
yosukekmt:feat/google-service-account

Conversation

@yosukekmt

Copy link
Copy Markdown

I have read the CONTRIBUTING.md file.

YES

What kind of change does this PR introduce?

Feature — adds a new google_service_account secret type.

What is the current behavior?

Closes #414

There is currently no way to authenticate agent requests to general Google APIs (Drive, Sheets, Storage, ...) as a GCP service account. The existing options don't cover this:

  • OAuth app connectors handle human-account (3-legged) flows.
  • The Vertex AI provider performs an SA JWT exchange, but is tied to cloud-platform scope and *-aiplatform.googleapis.com hosts.

Storing an SA key as a generic secret doesn't work either, since the JSON key is not itself a Bearer credential — it must be exchanged (signed JWT → access token) before injection.

What is the new behavior?

Users can register a Google Service Account JSON key as a secret. The gateway signs a short-lived RS256 JWT, exchanges it at oauth2.googleapis.com/token, caches the resulting access token in memory, and injects it as a Bearer header into matching requests. The private key never leaves the vault — agents only ever see request results. Details, following the pattern of existing code paths:

  • api: new secret type with Zod validation of the SA JSON
    (type: "service_account", private_key, client_email), metadata extraction limited to client_email / project_id, default hostPattern www.googleapis.com.
  • gateway: token resolution happens in the connect phase (mirroring the OpenAI OAuth refresh), keeping build_injections() synchronous. JWT claims omit sub (only meaningful for domain-wide delegation; causes invalid_grant without it). In-memory cache with ~50 min TTL and a 10 min safety margin, keyed by secret_id + a hash of the secret value so key rotation invalidates immediately. Injection is explicitly skipped for oauth2.googleapis.com to prevent circular injection if a user broadens the host pattern.
  • web: secret dialog gains a Google Service Account option with JSON paste/file upload, inline validation, and a read-only preview of the detected client_email / project_id. Secret cards show the client email. Scope is hardcoded to drive.readonly for the MVP and stated in the dialog; configurable scopes are planned as a follow-up.
  • tests: validation and service-layer tests (including that the private key never appears in metadata or error messages), and token-resolution tests covering cache hit / miss / expiry / rotation / exchange failure via an injected fetcher.
Screenshot 2026-07-08 at 12 03 41 AM Screenshot 2026-07-08 at 12 04 05 AM

Additional context

  • Verified end-to-end locally: two agents with selective secret grants, each assigned a different SA, can list only their own shared drive through the gateway (mode="mitm", token exchange logged once, then cache hits).
  • Cloud-edition note: the token cache uses the shared cache store, so on Cloud, short-lived access tokens would transit Redis. Flagging for review.
  • The JWT-exchange logic intentionally does not modify the Vertex AI path. If preferred, I'm happy to factor a shared exchange helper out of refresh_via_service_account() with scope/sub parameterized — kept out of this PR to minimize blast radius, per maintainer preference. Follow-ups (out of scope): configurable scopes via secret metadata, credential-stub interception for Google SDKs (as Vertex AI does).

@yosukekmt
yosukekmt force-pushed the feat/google-service-account branch from 2383b16 to acf1ac1 Compare July 7, 2026 16:40
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.

feat: Google Service Account secret type (JWT → Bearer token injection)

1 participant