Skip to main content
OIDC Workload Identity lets your Kubernetes pods access Enkryptify secrets without storing any static API tokens. Instead, Kubernetes generates a short-lived identity token for each pod, and Enkryptify validates it against your cluster’s public keys.

Why use OIDC instead of an API token?

With a static API token, you need to store an ek_live_* value as a Kubernetes Secret. If it leaks, an attacker can read your secrets until someone manually revokes it. With OIDC, Kubernetes generates a fresh token every hour. There is nothing to store, nothing to rotate and nothing to leak.
Static API TokenOIDC Workload Identity
Token lifetimeDays to months~1 hour (auto-rotated)
Stored as a secretYes (Kubernetes Secret)No
Blast radius if leakedUntil manually revokedUntil token expires (~1 hour max)
Tied to pod identityNoYes (specific service account only)

How it works

Kubernetes Cluster
┌─────────────────────────────────────────────────────┐
│  Pod                                                │
│                                                     │
│  1. Kubernetes mounts a short-lived JWT             │
│     at /var/run/secrets/tokens/token                │
│                                                     │
│  2. Your app reads this token                       │
│                                                     │
│  3. Your app sends it to Enkryptify ──────────────────► POST /v1/auth/oidc/exchange
│                                                     │         │
│  4. Enkryptify validates the token ◄──────────────────────────┘
│     against the cluster's public keys               │   Returns 15-min JWT
│                                                     │
│  5. Your app uses the JWT to fetch secrets ────────────► GET /v1/secrets/...
└─────────────────────────────────────────────────────┘
Enkryptify fetches your cluster’s public keys from its OIDC discovery endpoint and caches them. Your app never needs to worry about key management.

Prerequisites

  • A Kubernetes cluster with OIDC discovery enabled (all major cloud providers enable this by default)
  • kubectl configured for your cluster
  • An Enkryptify workspace with secrets to access

Setup

1

Gather your cluster information

You need three values:
FieldWhat it isExample
Issuer URLYour cluster’s OIDC issuer URLhttps://oidc.eks.us-east-1.amazonaws.com/id/ABC123
AudienceA string you choose (safety check)enkryptify
SubjectThe service account identitysystem:serviceaccount:production:my-api
aws eks describe-cluster \
  --name <cluster-name> \
  --query "cluster.identity.oidc.issuer" \
  --output text
Example: https://oidc.eks.us-east-1.amazonaws.com/id/ABCDEF1234567890
Verify your issuer is reachable:
curl -s https://<your-issuer-url>/.well-known/openid-configuration | jq .
You should see a JSON response with issuer and jwks_uri fields.
The issuer URL must be publicly reachable by the Enkryptify API so it can fetch the discovery document and public keys. All major cloud providers host this automatically. Self-managed clusters need to expose this endpoint.
The subject must exactly match the sub claim in your Kubernetes service account token. The format is always:
system:serviceaccount:<namespace>:<service-account-name>
Examples:
NamespaceService AccountSubject
defaultdefaultsystem:serviceaccount:default:default
productionmy-apisystem:serviceaccount:production:my-api
stagingbackendsystem:serviceaccount:staging:backend
To check what service account a deployment uses:
kubectl get deployment <name> -n <namespace> \
  -o jsonpath='{.spec.template.spec.serviceAccountName}'
If empty, the pod uses the default service account in its namespace.
2

Register the identity in Enkryptify

  1. Navigate to Credentials in the sidebar
  2. Click “Create credential”
  3. Select “Kubernetes” and click Continue
  4. Fill in the form:
    • Name — a label (e.g. prod-api-k8s)
    • Issuer URL — your cluster’s OIDC issuer URL
    • Audienceenkryptify (or your chosen audience string)
    • Subjectsystem:serviceaccount:<namespace>:<service-account-name>
    • PermissionRead only (unless your workload needs to write secrets)
    • Scope — restrict to the specific projects and environments this workload needs
  5. Click “Create identity”
3

Create a Kubernetes service account

If you don’t already have one, create a service account matching the subject from step 2.
service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-api          # Must match <service-account-name>
  namespace: production  # Must match <namespace>
kubectl apply -f service-account.yaml
4

Configure your pod with a projected token

Kubernetes can mount a short-lived JWT into your pod with a custom audience and expiration.
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-api
  template:
    metadata:
      labels:
        app: my-api
    spec:
      serviceAccountName: my-api

      containers:
        - name: my-api
          image: your-registry/your-app:latest
          env:
            - name: ENKRYPTIFY_TOKEN_PATH
              value: /var/run/secrets/tokens/token
            - name: ENKRYPTIFY_API_URL
              value: https://api.enkryptify.com
          volumeMounts:
            - name: enkryptify-token
              mountPath: /var/run/secrets/tokens
              readOnly: true

      volumes:
        - name: enkryptify-token
          projected:
            sources:
              - serviceAccountToken:
                  audience: enkryptify    # Must match the audience from step 2
                  expirationSeconds: 3600 # Token valid for 1 hour
                  path: token
kubectl apply -f deployment.yaml
After applying, Kubernetes will:
  1. Generate a JWT signed by the cluster’s private key
  2. Write it to /var/run/secrets/tokens/token inside the container
  3. Automatically rotate it before it expires (at ~80% of the expiration time)
5

Exchange the token in your app

The returned JWT is valid for 15 minutes. Before it expires, re-read the token file (Kubernetes may have rotated it) and exchange again.

Revoking an identity

When you decommission a service, revoke its OIDC identity so it can no longer authenticate:
  1. Go to Credentials
  2. Click the actions menu on the identity row
  3. Click Revoke
Any pods still using the identity will lose access within 15 minutes (when their current JWT expires).

One identity per service account

Each OIDC identity maps to one specific combination of issuer + audience + subject per workspace. If you have multiple apps that need different permissions, create a separate service account and OIDC identity for each:
  • my-api with read-write access to project A
  • my-worker with read-only access to project B

Troubleshooting

Enkryptify couldn’t find an identity matching the token’s iss, aud and sub claims.Check:
  1. The issuer URL matches your cluster’s issuer exactly (including trailing slashes)
  2. The audience matches what you configured in the projected volume
  3. The subject matches the service account: system:serviceaccount:<namespace>:<name>
  4. The identity has not been revoked
Debug — inspect the token your pod is receiving:
kubectl exec -it <pod-name> -n <namespace> -- sh
cat /var/run/secrets/tokens/token
Decode it to see the claims:
echo "<token>" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
Compare iss, sub and aud with what you registered in Enkryptify.
The token’s signature couldn’t be verified.Check:
  1. The issuer URL’s /.well-known/openid-configuration is publicly accessible
  2. The token hasn’t expired (check the exp claim)
  3. Your cluster’s OIDC keys haven’t rotated since the token was issued
Enkryptify couldn’t reach the issuer’s discovery endpoint.Check:
  1. The issuer URL is correct and publicly accessible
  2. There’s no firewall blocking outbound HTTPS from the Enkryptify API
  3. Try fetching it manually: curl https://<issuer-url>/.well-known/openid-configuration
The projected volume isn’t mounted correctly.Check:
  1. serviceAccountName in your pod spec matches the service account you created
  2. The volumeMounts name matches the volumes name
  3. Restart the deployment: kubectl rollout restart deployment <name> -n <namespace>
The OIDC exchange endpoint is rate-limited to 10 requests per minute per identity. Cache the returned JWT for its 15-minute lifetime instead of exchanging on every request.

Best practices

  • Use the narrowest scope possible. Only grant access to the projects and environments the pod needs.
  • Use read-only unless you need write. Most pods only read secrets.
  • One service account per app. Don’t share service accounts across different applications.
  • Set a short token expiration. expirationSeconds: 3600 (1 hour) is a good default. Kubernetes rotates it at ~80% of the lifetime.
  • Revoke identities you no longer need. When you decommission a service, clean up its OIDC identity.