Jwtlet is an RFC 8693 OAuth 2.0 token exchange service for participant context operations. Clients exchange a Kubernetes service account token for a signed JWT that encodes participant context claims. The service validates the incoming token against a Kubernetes OIDC issuer, resolves resource mappings and scope-to-claims mappings, and signs the resulting token via HashiCorp Vault.
Jwtlet runs two HTTP servers: a token exchange API (default port 8080) and a management API (default port 8081) for administering resource and scope mappings.
Requires Rust (stable toolchain).
# Build all crates
cargo build
# Build the release binary
cargo build --release -p jwtlet-serverUnit and integration tests:
cargo nextest runor
cargo testEnd-to-end tests run against a local kind Kubernetes cluster with Vault. Requires Docker
and kind installed.
cd e2e
make all # full cycle: cluster setup, build, test, cleanup
# or individual steps:
make setup # create KIND cluster and initialize Vault
make build # build and load the Docker image into the cluster
make test # run tests
make cleanup # tear down the clusterPass a TOML config file as the first argument, or set JWTLET_CONFIG_FILE:
jwtlet-server /path/to/config.toml
# or
JWTLET_CONFIG_FILE=/path/to/config.toml jwtlet-serverIndividual settings can also be overridden with environment variables using the JWTLET__ prefix (double underscore as
the nesting separator), e.g. JWTLET__TOKEN_EXCHANGE_PORT=9090.
# Ports and bind address
token_exchange_port = 8080 # default
management_port = 8081 # default; must differ from token_exchange_port
bind = "0.0.0.0"
# Storage backend: "memory" (default) or "postgres"
[storage_backend]
type = "memory"
# type = "postgres"
# url = "postgresql://user:pass@host:5432/jwtlet"
# Kubernetes OIDC validation
[k8s]
api_server_url = "https://kubernetes.default.svc" # required
cluster_issuer = "https://kubernetes.default.svc.cluster.local" # required
token_file = "/var/run/secrets/kubernetes.io/serviceaccount/token"
# Token issuance
[token]
client_audience = "https://kubernetes.default.svc.cluster.local" # required – expected aud of incoming tokens
audience = "my-service" # required – aud of issued tokens
participant_context_claim = "jwtlet_pc" # default
token_ttl_secs = 3600 # default
# Vault signing backend
[vault]
url = "http://vault:8200" # required
token_file = "/vault/secrets/.vault-token" # use token_file in production
# token = "s.xxxxx" # or a literal token for development
# Management API authorization
# Keys are Kubernetes service account identifiers; values are lists of roles.
# A caller must hold the "management:write" role to use any management endpoint.
[service_accounts]
"system:serviceaccount:my-namespace:my-sa" = ["management:write"]All management API endpoints (/api/v1/mappings, /api/v1/scopes) require a
Authorization: Bearer <token> header. The token must be a valid Kubernetes service
account token issued with the same audience as token.client_audience. Jwtlet
verifies the token via the Kubernetes TokenReview API and checks that the resolved
service account identity holds the management:write role.
To obtain a suitable token from within a cluster:
kubectl create token my-sa -n my-namespace \
--audience=https://kubernetes.default.svc.cluster.localSet RUST_LOG to control verbosity (trace, debug, info, warn, error):
RUST_LOG=debug jwtlet-server config.tomlApache-2.0