Temporal Cloud Proxy uses Uber Fx for dependency injection. At startup,Documentation Index
Fetch the complete documentation index at: https://mintlify.com/temporal-sa/temporal-cloud-proxy/llms.txt
Use this file to discover all available pages before exploring further.
cmd/main.go constructs an fx.App that wires together six modules — config, auth, codec, metrics, proxy, and transport — into a running gRPC service. Understanding this structure helps when extending the proxy with new authentication providers or KMS backends, or when debugging unexpected startup failures.
Module Overview
Each module is declared as anfx.Provide call inside a package-level Module variable. Fx resolves the dependency graph automatically and starts each provider in dependency order.
config
Reads the YAML configuration file (path supplied via the
--config CLI flag), validates the result, and exposes a ConfigProvider interface. Every other module depends on ConfigProvider to read server port, workload definitions, and encryption caching parameters.auth
Constructs an
AuthenticatorFactory during startup. The factory holds a providers map keyed on authenticator type string ("jwt", "spiffe"). When the proxy module calls NewAuthenticator(AuthConfig), the factory dispatches to the correct constructor and initialises the authenticator — including connecting to the SPIRE workload API socket for SpiffeAuthenticator.codec
Constructs an
EncryptionCodecFactory that holds a providers map keyed on encryption type ("aws-kms", "gcp-kms"). Per-workload codecs are created lazily by the proxy module via NewEncryptionCodec(EncryptionCodecOptions). Each codec wraps a Cipher which in turn wraps a CachingMaterialsManager wrapping the underlying KMS provider.metrics
Initialises a Prometheus registry backed by OpenTelemetry, registers all metric instruments (counters, histograms), and starts an HTTP server on the configured metrics port (default
9090) that serves the /metrics scrape endpoint. The MetricsProvider is invoked by cmd/main.go to ensure the server starts even if no other module directly depends on it.proxy
Implements
ProxyProvider, which exposes a grpc.ClientConnInterface. During newProxyProvider, one namespaceConnection is created per workload: a grpc.ClientConn to the Temporal Cloud endpoint (with TLS), an optional payload encryption interceptor, and an optional API key or mTLS namespace auth interceptor. All connections are stored in a connectionMux map keyed by workload_id and protected by a sync.RWMutex.transport
Creates a
grpc.Server, registers the WorkflowService handler using the Temporal SDK’s client.NewWorkflowServiceProxyServer, and listens on the configured host and port. The handler is backed by a workflowservice.WorkflowServiceClient that wraps the ProxyProvider’s ClientConnInterface, so every inbound RPC is forwarded through the proxy routing logic.Crypto Layer
The encryption subsystem is structured in layers to separate concerns — KMS I/O, caching, and AES-GCM cipher operations — while keeping each layer independently testable.MaterialsManager Interface
GetMaterial— generates a new data key (used during encryption). The KMS provider callsGenerateDataKey(AWS) orGenerateRandomBytes+Encrypt(GCP) and returns both the plaintext key and the KMS-encrypted key blob.DecryptMaterial— recovers a plaintext key from the encrypted key blob stored in payload metadata (used during decryption). The KMS provider callsDecryptand returns the recovered plaintext key.
CryptoContext is a map[string]string that carries purpose, encryption key ID, and namespace — it is passed directly to AWS KMS as the encryption context and to GCP KMS as additional authenticated data (AAD), binding the key to the specific operation context.
CachingMaterialsManager
CachingMaterialsManager wraps either KMS provider with an LRU cache (backed by github.com/hashicorp/golang-lru) to avoid a KMS call on every payload. Cache entries are validated against three independent expiry conditions:
- MaxAge — wall-clock age of the cached
MaterialsinceCreatedAt - MaxMessagesUsed — number of times the cached key has been used (incremented atomically inside a
sync.RWMutex) - MaxCache — maximum number of entries in the LRU; the LRU itself evicts the least-recently-used entry when the limit is reached
CryptoContext; decryption keys are keyed on a hash of both the CryptoContext and the encrypted key blob itself.
Cipher
Cipher holds a MaterialsManager and provides two methods:
Encrypt(ctx, EncryptInput) (ciphertext, encryptedKey, error)— fetches a material viaGetMaterial, generates a random 12-byte nonce, and encrypts the plaintext with AES-GCM using thePayloadContextas additional authenticated data (AAD). Returns thenonce || ciphertextbyte slice and the KMS-encrypted key blob.Decrypt(ctx, DecryptInput) (plaintext, error)— callsDecryptMaterialto recover the plaintext key, then opens the AES-GCM sealed message using thePayloadContextas AAD.
Codec
Codec (implements converter.PayloadCodec) wraps the Cipher and is the integration point with the Temporal SDK:
Encode— serialises each*commonpb.Payload, callsCipher.Encrypt, and wraps the result in a new payload with metadata fieldsencoding: binary/encrypted,encryption-key-id, andencrypted-data-key.Decode— skips payloads whose encoding is notbinary/encrypted, extractsencryption-key-idandencrypted-data-keyfrom metadata, callsCipher.Decrypt, and unmarshals the recovered bytes back into a*commonpb.Payload.
Authentication Layer
Worker-to-proxy authentication is handled by a two-level abstraction: a factory that selects the authenticator type, and anAuthenticator interface that the proxy calls on every request.
Authenticator Interface
JwtAuthenticator
Initialised with ajwks-url and a list of expected audiences. On Init, it fetches the JWKS from the URL and starts a background goroutine (via keyfunc) that refreshes the key set every 5 minutes. On Authenticate, it:
- Strips the
Bearerprefix from theauthorizationheader value - Parses and validates the JWT signature using the live JWKS
- Checks that at least one audience in the token’s
audclaim matches a configured audience - Verifies the
expclaim has not elapsed - Returns an
AuthenticationResultwithAuthenticated: true, thesubclaim asSubject, and the full claims map
SpiffeAuthenticator
Initialised with a list of allowedspiffe_ids, expected audiences, and a SPIRE agent endpoint (typically a Unix socket path). On Init, it connects to the SPIRE workload API using workloadapi.NewJWTSource — this call blocks until the agent is reachable. On Authenticate, it:
- Strips the
Bearerprefix - Calls
jwtsvid.ParseAndValidateagainst the liveJWTSource(which handles SVID signature verification and audience matching) - Checks that the validated SVID’s SPIFFE ID appears in the configured
spiffe_idsallow-list - Returns an
AuthenticationResultwithAuthenticated: trueand the SPIFFE ID asSubject
Close releases the JWTSource gRPC connection to the SPIRE agent.
Request Flow
The following describes the path of a single unary RPC from a Temporal worker to Temporal Cloud:Extension Points
Adding a New Authenticator
Implement the Authenticator interface
Create a new file in the
auth package. Implement all four methods: Type() string, Init(ctx, config), Authenticate(ctx, credentials), and Close().Register in the factory
Add a new entry to the
providers map inside newAuthenticatorFactoryProvider in auth/authenticator.go:Adding a New KMS Provider
Implement the MaterialsManager interface
Create a new file in the
crypto package. Implement GetMaterial (generate + encrypt a data key) and DecryptMaterial (decrypt the stored key blob):Register in the codec factory
Add a new entry to the
providers map inside newCodecFactoryProvider in codec/codec.go:The proxy only supports unary gRPC calls. The
NewStream method on ProxyProvider returns codes.Unimplemented unconditionally. Because the Temporal workflow service API is entirely unary, this is not a practical limitation for any standard Temporal workload.