Repository navigation
Add pluggable credential provider for calls to the local Temporal server - #309
Conversation
Add auth.CredentialProvider, provided by auth.Module and defaulting to EmptyCredentialProvider. Embedders can replace it with auth.WithCredentialProvider to attach per-RPC credentials, such as a bearer token, to every call the proxy makes to its local Temporal server (AdminService, WorkflowService, OperatorService, and replication streams). The credentials are applied only to the local (inbound) client, never to the remote side. createClient fails at startup if the provider returns no credentials, the local connection is not TCP, or the credentials require TLS and tcpClient.tls is not configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add examples/bearer-token, a runnable program that starts s2s-proxy with a custom auth.CredentialProvider. The provider attaches "authorization: Bearer <token>" to every call the proxy makes to its local Temporal server, reading the token from S2S_PROXY_EXAMPLE_BEARER_TOKEN on each call so rotated tokens take effect without a restart. The credentials require TLS, and the sample config uses a TCP local connection with TLS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CredentialProvider's credentials are now attached to calls to the local Temporal server only when the config sets local.credentials.enabled. The binary supplies how to get credentials; the config decides whether to send them. The proxy refuses to start when credentials are enabled but no CredentialProvider is configured, so a stock binary never calls an authZ-enabled server without a token. Config validation rejects credentials on a non-TCP local connection and any remote.credentials block, keeping the local credential off the remote side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pseudomuto
left a comment
There was a problem hiding this comment.
Added a few comments, but nothing blocking. Code LGTM, logic is right, tests are good. ![]()
| // WithCredentialProvider replaces the default CredentialProvider with the given one. | ||
| // Pass it to app.New as an extra fx option. | ||
| func WithCredentialProvider(provider CredentialProvider) fx.Option { | ||
| return fx.Decorate(func(CredentialProvider) CredentialProvider { return provider }) |
|
|
||
| // IsEmptyCredentialProvider reports whether the provider supplies no credentials. | ||
| func IsEmptyCredentialProvider(provider CredentialProvider) bool { | ||
| if provider == nil { |
There was a problem hiding this comment.
/nit I don't think you need this. _, ok := provider.(EmptyCredentialProvider) will return false for a nil provider.
| func NewProxy( | ||
| configProvider config.ConfigProvider, | ||
| logProvider logging.LoggerProvider, | ||
| credentialProvider auth.CredentialProvider, |
There was a problem hiding this comment.
Worth noting: this is a potentially breaking change for anyone who embeds the proxy. I don't imagine that's common, but it might come up at some point, and I just wanted to flag it.
| if auth.IsEmptyCredentialProvider(credentialProvider) { | ||
| return nil, fmt.Errorf("%s client: credentials are enabled but no CredentialProvider is configured", directionLabel) | ||
| } | ||
| clientOptions.PerRPCCredentials = credentialProvider.Get() |
There was a problem hiding this comment.
StreamForwarder.Run forwards the incoming metadata unchanged to the local server. If the remote peer sends an auth header, the local server gets two values (this token and the remote's).
I think grpc-go writes per-RPC creds first, so authHeaders[0] would still be ours, but the remote's token still reaches the local server. Is it worth deleting authorization from the forwarded metadata when credentials are enabled?
There was a problem hiding this comment.
I will strip the header in a follow-up PR.
Add auth.CredentialProvider, provided by auth.Module and defaulting to
EmptyCredentialProvider. Embedders can replace it with
auth.WithCredentialProvider to attach per-RPC credentials, such as a
bearer token, to every call the proxy makes to its local Temporal server
(AdminService, WorkflowService, OperatorService, and replication streams).
The credentials are applied only to the local (inbound) client, never to
the remote side. createClient fails at startup if the provider returns no
credentials, the local connection is not TCP, or the credentials require
TLS and tcpClient.tls is not configured.
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
What was changed
Why?
Checklist
Closes
How was this tested: