Back to Blog
Phase 2 2026-02-08

JIT Credentials via TokenRequest

How iddio eliminates long-lived cluster credentials by minting short-lived tokens on every request. The proxy never stores a permanent key to your cluster.

The Static Credential Problem

Most Kubernetes configurations use long-lived credentials: a client certificate or service account token stored in a kubeconfig file. These credentials are valid forever (or until manually rotated). If compromised, an attacker has permanent cluster access.

Iddio’s JIT credential mode eliminates this. Instead of storing a permanent credential, the proxy mints a short-lived token on every request using the Kubernetes TokenRequest API.

How TokenRequest Works

The Kubernetes TokenRequest API (available since v1.12, stable since v1.20) mints bound service account tokens with configurable lifetimes and audiences:

tokenReq := &authv1.TokenRequest{
    Spec: authv1.TokenRequestSpec{
        Audiences:         []string{"https://kubernetes.default.svc"},
        ExpirationSeconds: ptr(int64(300)), // 5 minutes
    },
}

token, err := clientset.CoreV1().
    ServiceAccounts(namespace).
    CreateToken(ctx, saName, tokenReq, metav1.CreateOptions{})

The resulting token is a JWT that expires after the configured duration. Kubernetes validates it normally — no special configuration needed on the cluster side.

Iddio’s JIT Flow

When JIT credentials are enabled, the proxy’s request lifecycle adds a token-minting step:

  1. Agent authenticates via mTLS or bearer token
  2. Request is classified by the tier engine
  3. Policy is evaluated — if denied, stop here
  4. Token is minted via TokenRequest with a 5-minute TTL
  5. Request is forwarded to the real cluster with the JIT token
  6. Token expires — no cleanup needed
func (p *Proxy) forwardWithJIT(r *http.Request) (*http.Response, error) {
    token, err := p.mintToken(r.Context())
    if err != nil {
        return nil, fmt.Errorf("jit mint: %w", err)
    }

    // Replace the agent's auth with the JIT token
    r.Header.Set("Authorization", "Bearer "+token.Status.Token)

    return p.transport.RoundTrip(r)
}

What the Proxy Stores

Without JIT:

  • ~/.kube/config with a long-lived client cert or token

With JIT:

  • A service account name and namespace (references, not secrets)
  • The cluster CA certificate (for TLS verification)
  • The ability to call TokenRequest (requires a bootstrap credential, but only for the proxy — not stored per-agent)

The key difference: the proxy stores the ability to mint credentials, not the credentials themselves. The bootstrap credential has a single, narrow permission: create on serviceaccounts/token in the configured namespace.

Token Lifecycle

JIT tokens have a deliberately short lifetime:

PropertyValue
Default TTL300 seconds (5 minutes)
Minimum TTL60 seconds (Kubernetes minimum)
Maximum TTL3600 seconds (1 hour, configurable)
Audiences["https://kubernetes.default.svc"]
Bound toService account in configured namespace

After 5 minutes, the token is invalid. There’s nothing to revoke, nothing to rotate, nothing to store. The next request mints a fresh token.

Configuration

JIT credentials are enabled with a flag:

iddio start \
  --cluster-url https://cluster:6443 \
  --credentials jit \
  --jit-service-account iddio-proxy \
  --jit-namespace iddio-system \
  --jit-ttl 300s

Or in the config file:

credentials:
  mode: jit
  jit:
    service_account: iddio-proxy
    namespace: iddio-system
    ttl: 300s

Security Properties

  • No long-lived credentials — even if the proxy host is compromised, there are no permanent tokens to steal
  • Time-bounded blast radius — a stolen JIT token expires in 5 minutes
  • Per-request isolation — each request gets its own token; one request’s token can’t be used for another
  • Audit trail — token minting events are recorded in the audit log with the requesting agent’s identity
  • Fail-closed — if TokenRequest fails, the request is denied (not forwarded without auth)

Compatibility

JIT credentials work with any Kubernetes cluster running v1.20+ (TokenRequest API GA). The cluster doesn’t need any iddio-specific configuration beyond:

  1. A service account in the iddio namespace
  2. A ClusterRoleBinding allowing that service account to create serviceaccounts/token
  3. The RBAC permissions you want the JIT tokens to have (bound to the service account’s roles)

Try It Yourself

Iddio is open source. Deploy a zero-trust command proxy for your AI agents in minutes.