Shared Codec Server for Bitovi platform + Temporal Cloud UI. GitHub: bitovi/temporal-codec.
This process is the HTTPS decode endpoint Temporal Cloud UI (and CLI) call to unwrap encrypted workflow payloads. It is a platform prerequisite (Argo wave 30), not something product teams deploy. Workers encrypt on a different path (temporal-proxy sidecar + the same per-namespace KMS CMK) and never call this service for encrypt.
Design intent and composition ownership live in platform ADR 0008:
bitovi-platform-services/docs/architecture/adr/0008-temporal-cloud-codec-server.md
| Concern | Owner |
|---|---|
This binary, Dockerfile, deploy/values.yaml |
this repo |
| Argo Application, charts, TCN / ContainerApp compositions | bitovi-platform-services |
| Worker image + business workflows | product app repos |
| Path | Who | What |
|---|---|---|
| UI / CLI decode | This service (POST /decode) |
Temporal Cloud UI browser → HTTPS → JWT authn/authz → AWS KMS unwrap for one namespace CMK |
| Worker encrypt | temporal-proxy sidecar in the worker pod | App ↔ localhost:7233 ↔ Temporal Cloud (ciphertext on the wire). Uses IRSA + the same namespace CMK. Not this process |
| Route | Auth | Role |
|---|---|---|
POST /decode |
Bearer JWT + namespace-bound authz | Primary UI path |
GET /health |
none | Liveness and readiness |
POST /encode |
off unless ENABLE_ENCODE=true |
Local/debug only |
Body shape matches Temporal converter.NewPayloadCodecHTTPHandler:
{"payloads":[...]}.
One CMK per Temporal Cloud namespace (minted by TemporalCloudNamespace
composition when codec is enabled):
alias/platform-managed/<namespace>-codec
# URI: awskms://alias/platform-managed/<namespace>-codec
Tags (platform): Purpose=temporal-codec, TemporalNamespace=<namespace>.
The Codec Server is configured with a template, for example:
ENCRYPTION_URI_TEMPLATE=awskms://alias/platform-managed/%s-codec
After the request is authorized for a namespace short name, decrypt uses only
that namespace’s key. It must not unwrap namespace A payloads with namespace B’s
KEK, and must not honor an arbitrary wire encryption-key-id under fleet-reader
IRSA.
- Namespace
codecServer.passAccessTokenis true so Cloud UI forwards the user access token. - Validate JWT against Temporal Cloud JWKS
(
https://login.tmprl.cloud/.well-known/jwks.jsonby default). X-Namespacealone never authorizes. It is client-supplied.- Token claims must permit the requested namespace (claim name configurable via
TEMPORAL_NAMESPACE_PERMISSION_CLAIM, defaultpermissions). Deny → 403, no KMS call. - CORS allows
https://cloud.temporal.iofor the browser only. CORS is not access control.
Claim mapping note: interface and unit tests exercise claim-based deny/allow.
Live Temporal Cloud JWT claim schema should be verified against a real
passAccessToken token before locking production authz assumptions.
Pin: github.com/temporalio/temporal-proxy v0.4.0 (pkg/crypto).
Metadata matches temporal-proxy encryption encoding (binary/encrypted,
encryption-key-id, encryption-dek). Static AES legacy codecs are not
compatible and must not be mixed with this history.
Never log: Authorization, raw JWTs, request or response payload bodies, DEKs.
Structured access logs may include status, duration, namespace, and principal
sub when safe.
See docs/architecture.md for package layout, the full decode request flow, auth and encryption models, and configuration reference.
flowchart TB
subgraph uiPath [UI decode path]
UI["Temporal Cloud UI\ncloud.temporal.io"]
CS["temporal-codec\ntemporal-codec.bitovi-tools.com"]
JWKS["login.tmprl.cloud\nJWKS"]
KMS1["AWS KMS\nalias/platform-managed/ns-codec"]
UI -->|"HTTPS POST /decode\nBearer + X-Namespace"| CS
CS -->|"validate JWT"| JWKS
CS -->|"authz then Decrypt DEK\nfor authorized ns only"| KMS1
CS -->|"cleartext payloads"| UI
end
subgraph workerPath [Worker encrypt path — codec server not involved]
App["Worker app"]
Proxy["temporal-proxy sidecar\n127.0.0.1:7233"]
Cloud["Temporal Cloud\nciphertext history"]
KMS2["AWS KMS same ns CMK\nIRSA TemporalNamespace=ns"]
App <-->|"cleartext gRPC local"| Proxy
Proxy <-->|"encrypted payloads"| Cloud
Proxy -->|"Encrypt/Decrypt DEK"| KMS2
end
subgraph platform [Platform materialization]
TCN["TemporalCloudNamespace\nComposition"]
Cap["ContainerApp\ntemporal-codec-reader IRSA"]
TCN -->|"mint CMK + alias\nregister codecServer.endpoint"| Cloud
Cap -->|"fleet Decrypt IRSA"| CS
end
sequenceDiagram
participant UI as Cloud UI browser
participant CS as temporal-codec
participant JWKS as Temporal Cloud JWKS
participant KMS as AWS KMS
UI->>CS: POST /decode Authorization Bearer X-Namespace
CS->>JWKS: Validate JWT signature and claims
CS->>CS: Namespace-bound authz from token
alt denied
CS-->>UI: 403 no KMS call
else allowed
CS->>KMS: Decrypt DEK for awskms://alias/platform-managed/ns-codec
CS-->>UI: Cleartext payloads
end
Failure domains: if this service is down, worker encrypt can still succeed
(proxy + IRSA). UI cleartext fails. Run multi-replica with a real /health
readiness probe.
| Identity | Actions | Scope |
|---|---|---|
| temporal-codec (fleet reader) | Decrypt (unwrap) | Keys tagged Purpose=temporal-codec (all namespace codec keys) |
| Worker / temporal-proxy | Encrypt + Decrypt | Purpose=temporal-codec and TemporalNamespace=<name> |
Application authz limits org members; the shared reader identity remains a host- compromise residual risk of one Codec Server (accepted trade-off until narrower reader identities exist).
- Go 1.26.4+ (module toolchain;
GOTOOLCHAIN=autoworks if needed) - Optional: AWS credentials + KMS access for live
awskms://decrypt acceptance - Docker only if you prefer running the image instead of
go run
Copy the example and adjust:
cp .env.example .env.env is gitignored. Never commit secrets.
| Variable | Default | Purpose |
|---|---|---|
PORT |
8080 |
HTTP listen port |
LOG_LEVEL |
info |
debug / info / warn / error |
ENCRYPTION_URI |
empty | Single default URI (testing://… local, or one awskms://). Required unless template or overrides supply URIs |
ENCRYPTION_URI_TEMPLATE |
empty | Production fleet form: awskms://alias/platform-managed/%s-codec (%s = authorized Cloud short name) |
ENCRYPTION_URI_OVERRIDES |
empty | Comma map ns=uri,ns2=uri2 for tests or break-glass |
TEMPORAL_CLOUD_JWKS_URL |
Cloud JWKS URL | Public HTTPS JWKS for JWT validation |
TEMPORAL_CLOUD_JWT_AUDIENCE |
https://saas-api.tmprl.cloud |
Expected JWT audience |
TEMPORAL_CLOUD_JWT_ISSUER |
empty | Optional issuer pin |
TEMPORAL_NAMESPACE_PERMISSION_CLAIM |
permissions |
Claim name carrying namespace membership list |
JWKS_REFRESH_INTERVAL |
1h |
JWKS refresh period |
CORS_ALLOWED_ORIGINS |
https://cloud.temporal.io |
CSV of allowed browser origins (not authz) |
AUTH_DISABLED |
false |
Local only: skips JWKS and allows any bearer. Never true in cluster |
ENABLE_ENCODE |
false |
When true, also serves POST /encode for local debug |
READ_HEADER_TIMEOUT |
10s |
HTTP server header timeout |
At process start, at least one of ENCRYPTION_URI, ENCRYPTION_URI_TEMPLATE,
or ENCRYPTION_URI_OVERRIDES must be set.
# Load env (zsh/bash; skip comments)
set -a && source .env && set +a
go run ./cmd/serverHealth:
curl -sS http://localhost:8080/healthWith auth disabled and testing:// encryption, you can exercise decode against
fixtures from unit tests. With real JWTs and awskms://, use Cluster IRSA or
your developer role that can kms:Decrypt the target alias.
Build binary:
go build -o /tmp/temporal-codec ./cmd/serverDocker:
docker build -t temporal-codec:dev .
docker run --rm -p 8080:8080 \
-e ENCRYPTION_URI='testing://KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio=' \
-e AUTH_DISABLED=true \
temporal-codec:devexport GOTOOLCHAIN=auto # if local go < 1.26.4
go test ./...Wire-compat (proxy seal → this codec’s decode, testing://, no Encode path):
go test ./internal/codec/ -run ProxyEncrypt_CodecDecode_wireCompat -count=1Optional live KMS gate (ADR acceptance gate 1):
WIRECOMPAT_AWSKMS_URI='awskms://alias/platform-managed/<ns>-codec' \
go test ./internal/codec/ -run awskmsOptional -count=1Auth unit tests cover authorized-namespace allow and wrong-namespace deny without calling KMS on deny.
This repo ships the image contract and interface sketch only. Cluster wiring lives in bitovi-platform-services.
-
App packaging (this repo)
Dockerfilemulti-stage Go imagedeploy/values.yaml—kind: server, port8080, hosttemporal-codec.bitovi-tools.com, multi-replica, env for JWKS + CORS +ENCRYPTION_URI_TEMPLATE, capabilitytemporal-codec-reader- CI pushes the image to ECR and write-backs
image.tag(same pattern as other platform container apps)
-
GitOps (platform repo)
gitops/apps/temporal-codec.yaml— Argo Application, sync wave 30, multi-source: this repo’s values +charts/interface/charts/platform-app-resources- Optional overlay under
gitops/temporal-codec/only if something cannot live in the app values
-
IAM
- Capability
temporal-codec-reader: fleetkms:Decrypt/DescribeKeyon keys taggedPurpose=temporal-codec(no key Create)
- Capability
-
Namespaces / workers (out of band of this repo)
TemporalCloudNamespacecomposition mintsalias/platform-managed/<namespace>-codecand registers:codecServer.endpoint: https://temporal-codec.bitovi-tools.compassAccessToken: true
- Product workers get temporal-proxy + namespace-scoped consumer IRSA via
the interface (
kind: worker+dependencies.temporal). They do not deploy this service.
Teams declare workers in their app interface values (kind: worker,
uses: [temporal], dependencies.temporal.namespace, 1Password client keys on
the proxy sidecar only). This repo is decode UI only; do not use it as the
worker guide.
Full field list and 1Password key names:
bitovi-platform-services docs/operations/temporal-cloud-namespace.md
(section Declaring a worker). Living example:
charts/tests/interface-tests/cases/temporal-worker.yaml.
Public URL: https://temporal-codec.bitovi-tools.com. TLS and DNS use the
platform wildcard and shared ALB (not configured in this repo).
From ADR 0008:
- Wire-compat decode of a real temporal-proxy
awskms://payload against KMS - Namespace-bound authz proven with real Cloud JWT claims
- Fail-closed worker encrypt (proxy path; not this HTTP service)
cmd/server/ process entrypoint
internal/config/ env loading
internal/auth/ JWKS validation, claims authz, middleware
internal/codec/ temporal-proxy pkg/crypto + payload adapter + wire tests
internal/httpapi/ routes, CORS, token-safe access logging
deploy/values.yaml platform ContainerApp interface sketch
Dockerfile
.env.example
Platform Application YAML, compositions, and goldens do not live here.
- ADR 0008 — Temporal Cloud Codec Server (platform design)
- Team worker interface (platform ops) — Declaring a worker
- Temporal Codec Server docs
- Data encryption
- temporal-proxy