Skip to content

feat(auth/gcp): add credentials-service REST client#1149

Open
wolo-lab wants to merge 10 commits into
mainfrom
wolo/auth-gcp-client
Open

feat(auth/gcp): add credentials-service REST client#1149
wolo-lab wants to merge 10 commits into
mainfrom
wolo/auth-gcp-client

Conversation

@wolo-lab

@wolo-lab wolo-lab commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Problem

ADK-go's auth subsystem needs to fetch end-user credentials from Google Cloud's
Agent Identity and IAM Connector credential services. There are no generated Go
client libraries for these (preview) services, and adk-go currently has no transport for them at all — while
adk-python already talks to these services.

Summary

  • Adds auth/gcp, a hand-rolled REST client over net/http (no new module
    dependencies) for the Agent Identity and IAM Connector credential services.
  • Client.RetrieveCredential(ctx, Request) takes a resource name, routes it to
    the right service (IAM Connector when the resource matches
    projects/*/locations/*/connectors/*, Agent Identity otherwise — same split
    as adk-python), retrieves an end-user credential, and maps the
    {header, token} result to an *auth.Credential (bearer token or header API
    key).
  • Polls with backoff while the service reports a non-interactive pending
    state (bounded by WithPollTimeout), and surfaces interactive consent as a
    consent-required error (auth.ErrConsentRequired).
  • Authenticates calls with Application Default Credentials (cloud-platform
    scope) unless a custom *http.Client is supplied via WithHTTPClient;
    endpoints are overridable via WithAgentIdentityEndpoint /
    WithConnectorEndpoint (used by tests).
  • Transport layer only — the CredentialProvider that resolves the acting user
    from the invocation context is a later step (it needs a shared
    agent.FromContext helper).

@wolo-lab
wolo-lab force-pushed the wolo/auth-core branch 4 times, most recently from 0136d03 to d70c44e Compare July 12, 2026 21:26
@wolo-lab
wolo-lab force-pushed the wolo/auth-gcp-client branch from 6d59920 to e49269e Compare July 16, 2026 11:57
@wolo-lab
wolo-lab marked this pull request as ready for review July 17, 2026 15:51
@wolo-lab
wolo-lab requested a review from hanorik July 17, 2026 15:51
@wolo-lab
wolo-lab changed the base branch from wolo/auth-core to main July 17, 2026 18:33
wolo-lab added 9 commits July 17, 2026 21:59
Add a hand-rolled REST client for the Google Cloud Agent Identity and IAM
Connector credential services. Given a resource name it routes to the right
service, retrieves an end-user credential, polls while the service reports a
non-interactive pending state, and maps the {header, token} result to an
auth.Credential (bearer or header API key), or to auth.ErrConsentRequired when
interactive consent is required.

No generated Go clients exist for these preview services (verified: no such
module on pkg.go.dev) and the surface is a single RPC, so this is hand-rolled
over net/http, authenticating calls with Application Default Credentials
(cloud-platform). No new module dependencies.

This is the transport layer only; the CredentialProvider that resolves the
acting user from the invocation context is a later step (it needs a shared
agent.FromContext helper).
- Add ErrConsentRejected / ErrPollTimeout sentinels (errors.Is-able) and wrap
  the rejection/timeout returns with them.
- Add a context-cancellation-during-poll test (no hang; surfaces
  context.Canceled).
- Move test helpers below the tests; use t.Context() in tests.
- Fix the URIConsentRequired initialism.
- Note the X-GOOG-API-KEY mirror as a follow-up TODO (needs an additive
  AdditionalHeaders field on auth.APIKeyCredential; non-breaking).
The auth core package redesigned Credential from a struct into an interface
(BearerCredential/APIKeyCredential/OAuth2Credential), so the client no longer
compiled against its base. Port the mapping accordingly, and apply the
remaining review feedback on the connector.

- mapCredential returns auth.Credential: auth.BearerCredential for an
  "Authorization: Bearer" header, auth.APIKeyCredential otherwise.
  RetrieveCredential's return type changes from *auth.Credential to
  auth.Credential.
- Connector: a done operation carrying no credential now returns an error
  instead of being treated as pending and polled to the timeout; drop the
  unused consentPending metadata field.
- Test ErrPollTimeout and the connector done-without-credential path.
Custom (non-bearer) headers now also set X-Goog-Api-Key alongside the
service's own header, matching adk-python's credential mapping.

Model the IAM Connector metadata consent_pending status explicitly (per
the v1alpha RetrieveCredentialsMetadata status oneof) instead of relying
on the unknown-status fall-through, and add a test for the connector
consent_rejected path (a real proto field that adk-python's connector
provider omits).
The wire responses emulated the services' result oneof with nullable-pointer
structs and mapped them to a retrieveResult inline in each retrieve* method.
Move that mapping into result() methods on agentIdentityResponse and
connectorOperation, so transport (doPost) is separated from interpretation
(now a pure, unit-testable step), and extract the duplicated {token, header}
success shape into a shared credentialPayload type. No behavior change.
Replace the retrieveResult struct + retrieveStatus enum (a fat struct whose
valid fields depended on the status) with a sealed outcome sum type
(credOutcome / pendingOutcome / consentOutcome / rejectedOutcome), so each arm
carries only its own fields and RetrieveCredential type-switches on it. The
per-service result() methods now return outcome. No behavior change.
Fold the eleven near-identical TestRetrieveAgentIdentity*/TestRetrieveConnector*
scenario tests (plus the done-without-credential case) into a single
table-driven TestRetrieveCredential, and make TestRetrieveValidatesRequest
table-driven too. The routing, HTTP-error, mapCredential, cancellation, and
poll-timeout tests stay separate (distinct setup/timing). No coverage change.
Trim four slightly wordy comments (outcome, credentialPayload, the connector
pending fall-through, and the TestRetrieveCredential doc) down to the essential
"why" after the outcome/table-driven refactors. No code changes; the remaining
comments were verified accurate and already terse.
adk-go overwhelmingly constructs objects with config structs (runner, agent,
llmagent, agenttool, skilltoolset, mcptoolset, agentregistry, gemini, ...);
only apigee and telemetry use functional options. Replace the Option/With* API
with a Config struct and NewClient(ctx, *Config) — a nil cfg or any zero field
uses defaults — matching the gemini/agenttool nil-config precedent and the
go-expert-review / adk-go-review "config structs" convention. No behavior change.
- Reject an oversized response body instead of feeding json.Unmarshal
  silently truncated bytes; cap error-body text so a large gateway page
  doesn't bloat the returned error.
- Trim trailing slashes on endpoint overrides so a configured
  "host/" can't produce a "//v1/..." path.
- Send Accept: application/json and include the operation error code in
  the connector failure message (which could be empty before).
- Remove the unused connectorRequest.ForceRefresh field; use errors.New
  for the constant validation errors.
- Add a NewClient test covering defaults and trailing-slash trimming.
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