This document describes authentication mechanisms for the HyperFleet API.
HyperFleet API supports the following authentication modes:
- Development Mode (No Auth): For local development and testing without authentication
- Development with JWT (Google Cloud): Local development with real JWT validation using Google identity tokens
- Production Mode (JWT Auth): JWT-based authentication with configurable issuer
For local development and testing, authentication can be disabled.
# Start service without authentication
make run-no-auth
# Access API without tokens
curl http://localhost:8000/api/hyperfleet/v1/clusters | jqexport HYPERFLEET_SERVER_JWT_ENABLED=false
./bin/hyperfleet-api serveWhen JWT is disabled, caller identity resolution is inactive. Audit fields (created_by, updated_by, deleted_by) fall back to system@hyperfleet.local.
When JWT is enabled, mutating requests (POST, PATCH, PUT, DELETE) that cannot resolve a caller identity are rejected with 401 Unauthorized. Read requests (GET, LIST) are allowed without identity.
The identity_header field is per-issuer and only takes effect when JWT is enabled. When configured, a trusted gateway can override the JWT claim by setting this header:
# Mutating request with identity header (JWT must still be valid)
curl -X POST http://localhost:8000/api/hyperfleet/v1/clusters \
-H "Authorization: Bearer $TOKEN" \
-H "X-HyperFleet-Identity: dev-user@local" \
-H "Content-Type: application/json" \
-d '{"kind":"Cluster","name":"my-cluster","spec":{}}'
# Read requests work without identity
curl http://localhost:8000/api/hyperfleet/v1/clusters | jqSee Caller identity for audit below for full details.
Important: Never disable authentication in production environments.
For local development with real JWT validation, you can use Google Cloud identity tokens. This gives you proper authentication and caller identity without deploying a dedicated identity provider.
- Google Cloud SDK installed
- Authenticated with
gcloud auth login
Configure via YAML (per-issuer JWT config, no CLI flags for configs):
server:
jwt:
enabled: true
configs:
- issuer_url: "https://accounts.google.com"
jwk_cert_url: "https://www.googleapis.com/oauth2/v3/certs"
header: Authorization
audience: "32555940559.apps.googleusercontent.com"
identity_claim: "email"
identity_claim_pattern: ""
identity_header: "X-HyperFleet-Identity"./bin/hyperfleet-api serve --config config.yaml \
--db-host localhost --db-port 5432 --db-name hyperfleet --db-username hyperfleetThe audience 32555940559.apps.googleusercontent.com is the default gcloud CLI OAuth client ID. It matches the aud claim in tokens generated by gcloud auth print-identity-token.
# Generate an identity token (valid for ~1 hour)
TOKEN=$(gcloud auth print-identity-token)
# List clusters
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8000/api/hyperfleet/v1/clusters | jq
# Create a cluster (created_by will be your Google email)
curl -X POST http://localhost:8000/api/hyperfleet/v1/clusters \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"kind":"Cluster","name":"my-cluster","spec":{}}'
# Override identity via header (header takes precedence over JWT)
curl -X POST http://localhost:8000/api/hyperfleet/v1/clusters \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-HyperFleet-Identity: gateway-user@corp.com" \
-d '{"kind":"Cluster","name":"my-cluster-2","spec":{}}'Google identity tokens are standard OIDC JWTs signed by Google's keys. The server validates them like any other JWT:
- Fetches Google's public keys from the configured
jwk_cert_urlin the issuer config - Verifies the RS256 signature
- Checks
issmatcheshttps://accounts.google.com - Checks
audmatches the configured audience - Extracts the
emailclaim for caller identity
You can inspect your token with:
gcloud auth print-identity-token | cut -d. -f2 | base64 -d 2>/dev/null | jqProduction deployments use JWT-based authentication with a configurable issuer.
# Start service with authentication
make run
# Access API with a valid JWT
curl -H "Authorization: Bearer ${TOKEN}" \
http://localhost:8000/api/hyperfleet/v1/clustersHyperFleet API validates JWT tokens using RS256 signature verification.
Token validation checks:
- Signature - Token signed by trusted issuer
- Issuer - Matches one of the configured
issuer_urlvalues inserver.jwt.configs - Audience - Matches the
audienceconfigured for that issuer - Expiration - Token not expired
- Claims - Required claims present
Token format:
Authorization: Bearer <jwt-token>
Example request:
curl -H "Authorization: Bearer ${TOKEN}" \
http://localhost:8000/api/hyperfleet/v1/clustersEach entry in server.jwt.configs supports the following fields:
| Field | Required | Default | Description |
|---|---|---|---|
issuer_url |
Yes | Expected iss claim for this issuer |
|
jwk_cert_url |
One of jwk_cert_url / jwk_cert_file |
JWKS endpoint URL for this issuer's public keys | |
jwk_cert_file |
One of jwk_cert_url / jwk_cert_file |
Path to a local JWKS file | |
header |
No | Authorization |
HTTP header to read the JWT from |
audience |
No | "" (any) |
Expected aud claim; skipped if empty |
identity_claim |
No | email |
JWT claim used as audit identity |
identity_claim_pattern |
No | "" (none) |
Regex the identity value must match; non-matching requests get 401 |
identity_header |
No | "" (disabled) |
HTTP header that overrides the JWT claim for audit identity (gateway-set only) |
Complete example with all fields:
server:
jwt:
enabled: true
configs:
- issuer_url: https://idp.example.com/realms/hyperfleet
jwk_cert_url: https://idp.example.com/realms/hyperfleet/protocol/openid-connect/certs
jwk_cert_file: ""
header: Authorization
audience: ""
identity_claim: email
identity_claim_pattern: ""
identity_header: ""Authentication (JWT validation) and caller identity (audit attribution) are separate concerns. When JWT is enabled, identity resolution is always active because identity_claim defaults to email. Audit fields (created_by, updated_by, deleted_by) are populated from the matched issuer's identity settings. When JWT is disabled, audit fields fall back to system@hyperfleet.local.
| Layer | Component | Responsibility |
|---|---|---|
| Outer | JWTHandler |
Validates Authorization: Bearer token |
| Inner | ResolveCallerIdentity middleware |
Resolves who is recorded as the actor |
The resolved identity is written to created_by on create, updated_by on update, and deleted_by on delete. Precedence: per-issuer identity header > JWT claim.
When identity resolution is configured, mutating requests (POST, PATCH, PUT, DELETE) that cannot resolve a caller identity are rejected with 401 Unauthorized. Read requests (GET, LIST) are allowed without identity.
Configure which JWT claim is used as the caller identity for each issuer:
server:
jwt:
configs:
- issuer_url: https://idp.example.com
jwk_cert_url: https://idp.example.com/certs
header: Authorization
audience: ""
identity_claim: email # or preferred_username, sub, etc.
identity_claim_pattern: ""
identity_header: ""When identity_header is set on an issuer config, a trusted gateway can set the caller identity via HTTP header. If the header is present and non-empty, it overrides the JWT claim for audit fields. JWT validation is still required.
server:
jwt:
configs:
- issuer_url: https://idp.example.com
jwk_cert_url: https://idp.example.com/certs
header: Authorization
audience: ""
identity_claim: email
identity_claim_pattern: ""
identity_header: X-HyperFleet-IdentitySecurity: Clients must not be able to set this header directly. Configure your ingress/gateway to strip the header from external requests and set it from the authenticated upstream user.
Identity values from both sources are validated: trimmed of whitespace, limited to 256 characters, and rejected if they contain control characters.
# Development (no auth)
export HYPERFLEET_SERVER_JWT_ENABLED=false
# Production (with auth) - configure issuers in config.yaml
export HYPERFLEET_SERVER_JWT_ENABLED=true
# Issuer configs must be set via YAML config file (server.jwt.configs list)See Deployment for complete configuration options.
Configure via Helm values:
# values.yaml — see "Issuer configuration reference" above for all fields
config:
server:
jwt:
enabled: true
configs:
- issuer_url: https://your-idp.example.com/auth/realms/your-realm
jwk_cert_url: https://your-idp.example.com/auth/realms/your-realm/protocol/openid-connect/certs
audience: https://your-api.example.comDeploy:
helm install hyperfleet-api oci://quay.io/redhat-services-prod/hyperfleet-tenant/hyperfleet/hyperfleet-api-chart:<tag> --values values.yamlNote: You may also choose to install from the ./charts folder, if you've cloned this repository locally.
401 Unauthorized
- Check token is valid and not expired
- Verify
issuer_urlandaudienceinserver.jwt.configsmatch token claims - Ensure
Authorizationheader is correctly formatted
Token debugging
# Decode JWT token (header and payload only, not verified)
echo $TOKEN | cut -d. -f2 | base64 -d | jq
# Check token expiration
echo $TOKEN | cut -d. -f2 | base64 -d | jq '.exp | todate'- Deployment - Authentication configuration and Kubernetes setup