Repository navigation
feat(go): declare the runtime security scheme on the agent card - #483
Merged
Merged
Conversation
The generated server declared its OIDC scheme only when the manifest set spec.server.auth.enabled, and never declared the static bearer-token mode. An agent started with A2A_AUTH_ENABLED or A2A_AUTH_TOKEN could protect /a2a while its card said nothing about how to authenticate. main.go now always carries advertiseAuthScheme, which reads the auth mode from runtime config at startup and adds the matching scheme and requirement to the served card: openIdConnect for A2A_AUTH_ENABLED, http bearer for A2A_AUTH_TOKEN. A scheme the manifest declares under the same name is kept. A2A_AUTH_TOKEN is listed in the generated CONFIGURATIONS.md and .env.example. Closes #482
Contributor
|
🎉 This PR is included in version 0.67.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This was referenced Oct 5, 2026
edenreich
added a commit
to inference-gateway/cli
that referenced
this pull request
Oct 5, 2026
… a2a-auth example (#1542) ## Summary Lets `infer` authenticate to A2A agents that protect `/a2a`. An agent entry in `agents.yaml` can carry an `auth` block with a static bearer token or an OIDC client-credentials grant. Closes #1514 and closes #1512, which adds `examples/a2a-auth` on top of it. ```yaml agents: - name: research url: https://research.example.com auth: token_env: RESEARCH_AGENT_TOKEN - name: billing url: https://billing.example.com auth: oidc: client_id: infer client_secret_env: BILLING_AGENT_CLIENT_SECRET audience: billing-agent ``` ## How it works - **Secrets stay in the environment.** The `auth` block names environment variables and never holds a value. `LoadYAML` expands `${VAR}` over the whole file and every `infer agents add/update/remove` writes the result back, so a `token: ${VAR}` field would have ended up on disk in plain text. With variable names, the file, `infer agents list` and `infer agents show` cannot leak a secret. - **Every request is covered.** The credentials are added by a transport inside `a2ainfra.NewClient`, the one constructor behind the agent card fetch, `SendMessage`, `GetTask` polling, `CancelTask`, the liveness probe and `infer agents status`. ADK `Config.Headers` does not reach the card fetch, a transport does. - **Credentials stay on their agent.** `agent_url` comes from the model, so the transport only authenticates requests to the scheme, host and port of the configured agent. A redirect target or another port on the same host gets nothing. - **The agent card says where tokens come from.** As the A2A protocol defines, the agent declares how to authenticate in the `securitySchemes` of its card. The CLI reads the token endpoint from there: the discovery URL of an `openIdConnect` scheme, or the token URL of an `oauth2` client-credentials flow, with the scopes the card's `securityRequirements` list. `agents.yaml` only holds the client id, the secret variable and an optional audience. - **Optional `issuer_url`, pin and fallback.** The client secret goes to whatever endpoint the card names, and the card is fetched unauthenticated. With `auth.oidc.issuer_url` set, a card that points outside that issuer is refused before the secret is sent. The same field is the fallback for a card that declares no scheme at all, as agents generated before ADL CLI v0.67.0 do. - **OIDC tokens are reused.** One `clientcredentials` token source per grant lives for the process, fetches once and refreshes shortly before expiry. This adds `golang.org/x/oauth2` as a direct dependency. - **Auth failures are named.** A 401 or 403, a missing variable or a failed token request reaches the model as `Authentication failed for A2A agent "<name>": ...`, without the response body. Background polling ends the job on it instead of retrying forever. ## Example (#1512) `examples/a2a-auth/` runs one `mock-agent` 0.5.0 behind `A2A_AUTH_TOKEN` and one behind OIDC against Keycloak, with a tokenless mock model and no provider API key. The credentials are in the example's `.infer/agents.yaml`, with the secrets read from the environment. Each agent declares its scheme on its card, so the OIDC entry only holds the client and `infer` reads the token endpoint from the card. | Task | Result | | --- | --- | | `task token` | delegation to the token agent completes | | `task oidc` | delegation to the OIDC agent completes with a token from Keycloak | | `task wrong-token` | `Authentication failed for A2A agent "token-agent": the agent rejected the request with status 401` | | `task missing-token` | `Authentication failed for A2A agent "token-agent": environment variable TOKEN_AGENT_TOKEN is not set` | | `task wrong-secret` | `Authentication failed for A2A agent "oidc-agent": fetching the OIDC token: the token endpoint answered with status 401 unauthorized_client` | Keycloak and the OIDC agent share a network namespace, so the issuer is `http://localhost:8090/realms/a2a` for `infer` on the host and for the agent in its container. `docs/examples.md` lists the example and `docs/agents-configuration.md` links it. ## Testing - `infrastructure/auth_test.go`: the header on the card fetch, `SendTask`, `GetTask` and `CancelTask`, no header on another origin or after a cross-origin redirect, the OIDC token endpoint read from an `openIdConnect` and an `oauth2` card scheme, one token fetch across requests plus a refetch once the token expires, a card with no usable scheme or outside the pinned issuer refused before the secret is sent, the auth-failure message for 401 and 403, an unset variable. - `config/agents_test.go`: origin matching and a load and rewrite of `agents.yaml` that never contains the secret. - `submit_task_error_handling_test.go`: a rejected submission and a rejected poll both end with the named error. - Manual: all five example scenarios above and `task status` run against `mock-agent` 0.5.0 and Keycloak 26.3.2 with a local build, with no `issuer_url` configured. - `task precommit:run` passes. ## Not in this PR - `infer agents add/update` flags for `auth`. Edit `agents.yaml` directly for now. - Credentials for an agent that is only in `INFER_A2A_AGENTS` with no `agents.yaml` entry. - Authentication on artifact downloads. - ADK v0.33.0 has no typed HTTP error, so a 401 or 403 is recognised from the error text. A test pins the wording. A typed error in `inference-gateway/adk` would remove the text match and the roughly 6 second retry before a credential error surfaces on task requests. ## Affected repos - `inference-gateway/docs` needs the new `auth` block documented, tracked in inference-gateway/docs#985. - `inference-gateway/adl-cli`: generated Go agents did not declare their runtime auth mode in the card's `securitySchemes`. Fixed in inference-gateway/adl-cli#483 (closes inference-gateway/adl-cli#482), released in v0.67.0. - `inference-gateway/mock-agent`: its hand-maintained `main.go` was re-synced with the template in inference-gateway/mock-agent#172, released in 0.5.0, which the example pins. - inference-gateway/adl-cli#485 fixes an older template bug found during that sync, a doc comment glued onto the closing brace above it. It does not affect this PR. - `inference-gateway/adk` could expose a typed HTTP status error (optional follow-up, see above).
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
A generated Go agent now declares on its agent card how
/a2ais secured, for both auth modes the ADK supports and from runtime config alone.Closes #482.
Before,
main.go.tmplemitted theserver.OIDCSecuritySchemesblock only when the manifest setspec.server.auth.enabled, and nothing declared the static bearer-token mode. An agent started withA2A_AUTH_ENABLED=trueorA2A_AUTH_TOKENcould protect/a2awhile its card hadsecuritySchemes: null, and the ADK loggedauthentication is enabled but the agent card declares no securitySchemes.Changes
main.go.tmplalways generatesadvertiseAuthScheme, called after the server is built. It readscfg.A2A.AuthConfigat startup and adds the matching scheme and requirement to the served card:openIdConnectwhenA2A_AUTH_ENABLEDis true,httpbearer whenA2A_AUTH_TOKENis set. With no auth it does nothing.spec.card.securitySchemes) is kept as written, and no duplicate requirement is added. The old block overwrote it.A2A_AUTH_TOKENis listed in the generatedCONFIGURATIONS.md, and as an optional commented entry in.env.examplefor manifests withoutspec.server.auth.enabled.Testing
TestGoMainTemplate_AdvertisesRuntimeAuthSchemerendersmain.gowith and withoutspec.server.auth.enabled.go-agent-builtin-tools(auth off in the manifest) andgo-agent-auth, built them and read the served card:securitySchemesA2A_AUTH_TOKENbearerhttp scheme and requirementA2A_AUTH_ENABLEDagainst KeycloakopenIdscheme with the issuer's discovery URL and requirementbearerandapiKeydeclaredA2A_AUTH_TOKENinferfrom feat(a2a): authenticate to agents with per-agent credentials, with an a2a-auth example cli#1542 authenticated to the regenerated OIDC agent through Keycloak with noissuer_urlconfigured, reading the token endpoint from the card.task fmt lint testpasses.Follow-ups
inference-gateway/mock-agent: itsmain.gois in.adl-ignoreand synced with the template by hand, so it needs a re-sync after this is released. Not filed yet.inference-gateway/cli:examples/a2a-authcan then dropauth.oidc.issuer_url.