Skip to content

Allow the egress gateway to use more than one credential provider - #3409

Open
Max Thompson (thompsonmax) wants to merge 8 commits into
agent-substrate:mainfrom
thompsonmax:multi-credential-providers
Open

Max Thompson (thompsonmax) wants to merge 8 commits into
agent-substrate:mainfrom
thompsonmax:multi-credential-providers

Conversation

@thompsonmax

@thompsonmax Max Thompson (thompsonmax) commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #1661.

We plan an out-of-tree credential provider that exchanges an actor's JWT for a cloud access token (RFC 8693), so actors can call Google Cloud APIs as themselves. Clusters will also run Secret Manager or Kubernetes Secrets providers. Today the egress gateway dials one provider and refuses URIs naming any other, which prevents us from supporting multiple credential providers.

  • atenet's --credential-provider-name, -address and -server-name repeat once per provider. The gateway sends each ate-secret:// URI to the provider its host names, and still fails closed on any other.
  • ate-setup's --credential-provider also takes a JSON array, rendered for both the Envoy and agentgateway dataplanes.

The single-object JSON and single flags behave as before, except that atenet now rejects an empty --credential-provider-address, which used to turn injection off, and an address without a --credential-provider-name (ate-setup always passes both; --credential-provider-name= keeps the unchecked dev mode). Providers share one CA and client certificate, as they do today. Docs follow in #3410.

Tested with unit tests. Not run: kind e2e with two providers, which is worth a follow-up, or agentgateway with more than one entry.

AI use: AI-assisted, I reviewed and tested it.

The egress gateway dialed one credential provider and refused any
credential URI naming another. Make the provider name, address and
server name flags repeatable, dial each provider, and send each URI
to the provider its host names. One of each flag behaves as before.

Signed-off-by: Max Thompson <maxthomp@google.com>
--credential-provider also takes a JSON array of provider objects.
ate-setup renders one set of egress flags, or one agentgateway
credentialProviders entry, per provider, and deploys the bundled
Kubernetes Secrets provider when k8s.io is among them.

Signed-off-by: Max Thompson <maxthomp@google.com>
@thompsonmax Max Thompson (thompsonmax) added this to the M3 milestone Oct 28, 2026
@thompsonmax Max Thompson (thompsonmax) added the prio/P0 Highest priority / required for next milestone label Oct 28, 2026
Comment thread cmd/ate-setup/internal/config/config.go
Decode --credential-provider once and switch on whether it is an
object or an array, instead of looking for a leading bracket, and
document the three accepted forms.

Signed-off-by: Max Thompson <maxthomp@google.com>
Signed-off-by: Max Thompson <maxthomp@google.com>

@bowei Bowei Du (bowei) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise LG, some comments

Comment thread cmd/atenet/internal/router/egress/egress.go Outdated
Comment thread cmd/atenet/internal/router/egress/egress.go
Comment thread cmd/atenet/internal/router/egress/credentials.go Outdated
With the repeatable flag, --credential-provider-address= became one
provider with an empty dial target instead of turning injection off.
Refuse it at startup, and correct the comments on what happens with no
provider: a request that needs an injection is denied.

Signed-off-by: Max Thompson <maxthomp@google.com>
An entry such as null reported "name is required". Reject a non-object
entry by its JSON type, and handle the error from re-encoding each entry.

Signed-off-by: Max Thompson <maxthomp@google.com>
Document the providers map on the Handler field and point New at it.
The catch-all now applies only when "" is the sole entry, as the field
says, instead of whenever a "" key is present.

Signed-off-by: Max Thompson <maxthomp@google.com>
Signed-off-by: Max Thompson <maxthomp@google.com>

@thockin Tim Hockin (thockin) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to approve but seek a response, especially on the repeated flag question

const credentialProviderUsage = `{"name":"k8s.io"} for the bundled Kubernetes Secrets provider, ` +
`{"enabled":false} to turn egress credential injection off, ` +
`or {"name":"<provider>","address":"<host>:<port>"} for a provider you deploy`
`or {"name":"<provider>","address":"<host>:<port>"} for a provider you deploy; ` +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be better as a repeated flag, rather than a JSON list?

--credential-provider '{...}' --credential-provider value '{...}'

?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, plus one

fs.StringVar(&cfg.CredentialProvider.CAFile, "credential-provider-ca-file", "", "CA the credential provider's serving certificate must chain to; required unless --credential-provider-insecure is set")
fs.StringVar(&cfg.CredentialProvider.ClientCert, "credential-provider-client-cert", "", "Credential bundle presented to the credential provider as the client certificate; required unless --credential-provider-insecure is set")
fs.StringVar(&cfg.CredentialProvider.ServerName, "credential-provider-server-name", "", "SAN/SNI expected on the credential provider's serving certificate")
fs.StringArrayVar(&cfg.CredentialProvider.ServerNames, "credential-provider-server-name", nil, "SAN/SNI expected on a credential provider's serving certificate. Repeat once per --credential-provider-address, in the same order, or omit")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FTR: multiple parallel repeated flags are almost always the wrong choice

func (h *Handler) providerCredential(ctx context.Context, ref resources.ActorRef, dest egresspolicy.Destination, uri string) ([]byte, error) {
if h.provider == nil {
if len(h.providers) == 0 {
slog.ErrorContext(ctx, "egress denied: policy requires credential injection but no credential provider is configured",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this case not covered by the names a provider this gateway does not serve error below? Seems like a better error, too?

Candidate for cleanup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prio/P0 Highest priority / required for next milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants