Skip to content

Commit 0a3fe7b

Browse files
committed
Adding info for new TokenProvider plugin
1 parent 0bb7775 commit 0a3fe7b

3 files changed

Lines changed: 185 additions & 0 deletions

File tree

docs/production-deployment/self-hosted-guide/security.mdx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,105 @@ Related read:
339339

340340
- [How to secure a Temporal Service](/security)
341341

342+
### What is a TokenProvider Plugin? {#token-provider}
343+
344+
The Token Provider component is a pluggable component that attaches an authentication token to outbound cross-cluster RPCs.
345+
Where the `ClaimMapper` and `Authorizer` plugins protect the receiving end of a request, `TokenProvider` protects the sending end.
346+
When a Temporal Service replicates Workflows, Schedules, or Namespaces to a peer cluster, `TokenProvider` supplies the bearer token that the peer's `ClaimMapper` validates.
347+
348+
A typical approach pairs a JWT-emitting `TokenProvider` on the sender with the [default JWT `ClaimMapper`](#default-jwt-claimmapper) on the receiver.
349+
350+
`TokenProvider` is a single-method interface:
351+
352+
```go
353+
type TokenProvider interface {
354+
GetToken(ctx context.Context, rpcAddress string) (token string, expiresAt time.Time, err error)
355+
}
356+
```
357+
358+
`GetToken` is called for each outbound cross-cluster connection.
359+
360+
- `rpcAddress` is the receiver cluster's `host:port`. Providers commonly use this value as the JWT's `aud` claim to scope each token to a specific receiver.
361+
- `expiresAt` lets the credential cache rotate tokens before they expire. Return `time.Time{}` if the token never expires.
362+
363+
#### Required claims on the receiver
364+
365+
Cross-cluster RPCs target the receiver's `AdminService`, which the default `Authorizer` only admits with `Claims{System: RoleAdmin}`.
366+
Tokens carrying lower roles are rejected.
367+
368+
With the default JWT `ClaimMapper`, the token must carry a `permissions` claim with the entry `temporal-system:admin`.
369+
370+
```
371+
{
372+
"permissions":[
373+
"temporal-system:admin"
374+
]
375+
}
376+
```
377+
378+
A custom `ClaimMapper` can recognize any JWT shape, such as an OAuth-style `scp` scope or a custom claim, and translate it into the same `Claims{System: RoleAdmin}` result.
379+
380+
#### Stream lifecycle
381+
382+
Cross-cluster replication runs over long-lived gRPC streams.
383+
The token is attached when a stream is opened, and the receiver's `ClaimMapper` and `Authorizer` evaluate it at that point.
384+
Already-open streams are not re-checked when the token later expires.
385+
Rotation and revocation therefore only take effect when a stream is reestablished, for example after a reconnect or a process restart.
386+
387+
#### TokenCredentials caching
388+
389+
Tokens returned by `GetToken` are cached per outbound connection by `auth.TokenCredentials`.
390+
The credential refreshes proactively within a configurable grace window before expiry.
391+
If a fetch fails before the token is hard-expired, the credential falls back to the last cached token.
392+
393+
Configure the refresh behavior through `global.authorization.remoteClusterAuth.graceWindow`, a Go duration string such as `30s` or `5m`.
394+
The default is `30s`.
395+
396+
#### Transport security
397+
398+
`auth.TokenCredentials` requires transport security, so the gRPC runtime refuses to attach the credential to a plaintext dial.
399+
Configuring `WithTokenProvider` therefore requires also configuring TLS for the destination, either through [`global.tls.remoteClusters`](/references/configuration) in the YAML config or by passing a custom provider through [`temporal.WithTLSConfigFactory`](/references/server-options#withtlsconfigfactory).
400+
401+
:::note
402+
403+
If `WithTokenProvider` is set but no remote-cluster TLS source is configured, the Temporal Service fails to boot with a directed error message.
404+
This catches the misconfiguration at startup rather than on the first cross-cluster RPC.
405+
406+
:::
407+
408+
#### Fail-closed mode
409+
410+
Set `global.authorization.remoteClusterAuth.require: true` to require a non-empty token on every outbound remote-cluster RPC.
411+
When `require` is `true`, the Temporal Service refuses to start if no `TokenProvider` is configured, and the credential returns `Unauthenticated` rather than sending an empty `authorization` header.
412+
413+
#### JWT helpers
414+
415+
For providers that emit JWTs, `auth.ParseJWTExpiry(token string) time.Time` extracts the `exp` claim without verifying the signature.
416+
This lets a `TokenProvider` return the JWT's own expiry as the `expiresAt` so cache rotation tracks the token's real lifetime.
417+
418+
#### Configuration
419+
420+
Configure your `TokenProvider` with the [`temporal.WithTokenProvider`](/references/server-options#withtokenprovider) server option.
421+
Pair it with the receiver-side plugins so peers validate what the sender attaches.
422+
423+
```go
424+
temporalServer, err := temporal.NewServer(
425+
temporal.WithTokenProvider(myTokenProvider),
426+
temporal.WithAuthorizer(authorization.NewDefaultAuthorizer()),
427+
temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper {
428+
logger := getYourLogger()
429+
return authorization.NewDefaultJWTClaimMapper(
430+
authorization.NewDefaultTokenKeyProvider(cfg, logger),
431+
cfg,
432+
logger,
433+
)
434+
}),
435+
)
436+
```
437+
438+
When `TokenProvider` is not configured, outbound cross-cluster RPCs carry no `authorization` header.
439+
This is the default for deployments that don't replicate to peers, or that rely on transport-level (mTLS) authentication alone.
440+
342441
## Data Converter {#data-converter}
343442

344443
Each Temporal SDK provides a [Data Converter](/dataconversion) that can be customized with a custom [Payload Codec](/payload-codec) to encode and secure your data.

docs/references/configuration.mdx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,81 @@ global:
210210
- `internode.client.rootCaFiles`
211211
- `frontend.server.clientCaFiles`
212212

213+
### authorization
214+
215+
The `authorization` section configures the Temporal Service's authentication.
216+
It selects the pluggable components that validate incoming gRPC tokens, the signing keys to trust, and the credentials this server attaches to outbound cross-cluster replication RPCs.
217+
218+
It contains two structural subsections that mirror the direction of traffic:
219+
220+
- [`jwtKeyProvider`](#jwtkeyprovider): Supplies the signing keys used to verify inbound JWTs.
221+
- [`remoteClusterAuth`](#remoteclusterauth): Controls the bearer tokens attached to outbound cross-cluster RPCs.
222+
223+
The top-level fields select and tune the inbound plugins:
224+
225+
- `authorizer` - _string_ - _Default:_ `""`.
226+
Selects the inbound authorizer. Empty string disables authorization (the no-op authorizer permits every request); `default` enables Temporal's built-in role-based authorizer. The value is case-insensitive.
227+
- `claimMapper` - _string_ - _Default:_ `""`.
228+
Selects the `ClaimMapper` that extracts roles from a verified token. Empty string disables claim mapping; `default` enables the built-in JWT `ClaimMapper`.
229+
- `audience` - _string_ - _Default:_ `""`.
230+
Required `aud` claim value that inbound JWTs must contain. When empty, audience validation is skipped.
231+
- `authHeaderName` - _string_ - _Default:_ `authorization`.
232+
gRPC metadata header from which the `ClaimMapper` reads the bearer token.
233+
234+
See [How to secure a Temporal Service](/self-hosted-guide/security) for the conceptual model behind `ClaimMapper`, `Authorizer`, and `TokenProvider` and how the plugins fit together.
235+
236+
A minimal example that enables JWT-based inbound auth using Temporal's defaults:
237+
238+
```yaml
239+
global:
240+
authorization:
241+
jwtKeyProvider:
242+
keySourceURIs:
243+
- https://idp.example.com/.well-known/jwks.json
244+
refreshInterval: 1m
245+
authorizer: default
246+
claimMapper: default
247+
audience: temporal-frontend
248+
```
249+
250+
#### jwtKeyProvider
251+
252+
Configures the source of signing keys used by the default `ClaimMapper` to verify inbound JWTs.
253+
254+
- `keySourceURIs` - _list of strings_.
255+
URLs to fetch JWKS-formatted public keys from. The default `ClaimMapper` fetches and caches the union of keys returned by each URI.
256+
- `refreshInterval` - _Go duration string_ (for example `1m`, `5m`) - _Default:_ `0`.
257+
How often the key set is refetched. Zero (the default) disables periodic refresh, so keys are loaded once at startup and never rotated.
258+
259+
#### remoteClusterAuth
260+
261+
Controls outbound bearer tokens carried on cross-cluster RPCs by a [`TokenProvider`](/self-hosted-guide/security#token-provider).
262+
This block has no effect unless a `TokenProvider` is also configured via [`temporal.WithTokenProvider`](/references/server-options#withtokenprovider).
263+
264+
- `require` - _boolean_ - _Default:_ `false`.
265+
When `true`, every outbound cross-cluster RPC must carry a non-empty token; the credential returns `Unauthenticated` rather than sending an empty `authorization` header.
266+
The Temporal Service refuses to start when `require` is `true` but no `TokenProvider` is configured.
267+
- `graceWindow` - _Go duration string_ (for example `30s`, `5m`) - _Default:_ `30s`.
268+
How long before a token's `expiresAt` the credential cache refreshes proactively.
269+
If a refresh fails, the credential falls back to the last cached token until its hard expiry.
270+
271+
A combined example pairing inbound JWT validation with outbound replication-stream auth on a sender cluster:
272+
273+
```yaml
274+
global:
275+
authorization:
276+
jwtKeyProvider:
277+
keySourceURIs:
278+
- https://idp.example.com/.well-known/jwks.json
279+
refreshInterval: 1m
280+
authorizer: default
281+
claimMapper: default
282+
audience: temporal-frontend
283+
remoteClusterAuth:
284+
require: true
285+
graceWindow: 30s
286+
```
287+
213288
## persistence
214289

215290
The `persistence` section holds configuration for the data store/persistence layer.

docs/references/server-options.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ s, err := temporal.NewServer(
117117
)
118118
```
119119

120+
### WithTokenProvider
121+
122+
Configures a [`TokenProvider`](/self-hosted-guide/security#token-provider) that supplies bearer tokens for outbound cross-cluster RPCs.
123+
`TokenProvider` is defined in the `go.temporal.io/server/common/rpc/auth` package.
124+
125+
```go
126+
s, err := temporal.NewServer(
127+
temporal.WithTokenProvider(myTokenProvider),
128+
)
129+
```
130+
120131
### WithCustomMetricsReporter
121132

122133
Sets a custom tally metric reporter.

0 commit comments

Comments
 (0)