Kubernetes
A pod authenticates with the service account it runs as. Project a token for penv, then wrap your process with penv run.
A pod authenticates with the service account it runs as. The cluster signs a token that names that account, penv checks it against a trust you set up once, and no key lives in a manifest.
Two routes reach a pod. This page is the one where penv runs inside the container. The other route hands values to External Secrets Operator, which is its own page.
Set up the trust
Open Machine Identities then Connect a Platform and pick the entry that matches your cluster. Five entries exist because the issuer is discovered differently on each.
| Entry | Where the issuer comes from |
|---|---|
| Kubernetes | You paste the cluster issuer URL. |
| Amazon EKS | You paste the URL aws eks describe-cluster prints. |
| Google Kubernetes Engine | penv builds it from three values your Google Cloud console shows. |
| Azure Kubernetes Service | You paste the URL az aks show prints, trailing slash included. |
| Oracle Container Engine for Kubernetes | penv builds it from the region and the discovery key, with the realm under Advanced. |
Every entry also asks for the namespace and the service account. The subject is always
system:serviceaccount:<namespace>:<service-account>, matched exactly. The audience is always your
workspace id.
The cluster issuer must be reachable from the internet. penv fetches the issuer's discovery document when the trust is bound, so an issuer nobody outside the cluster can read is a trust that can never bind. Each managed cluster above publishes one.
Finding your issuer
| Cluster | Command |
|---|---|
| Any | kubectl get --raw /.well-known/openid-configuration |
| EKS | aws eks describe-cluster --name $CLUSTER --query cluster.identity.oidc.issuer |
| AKS | az aks show -n $CLUSTER -g $RG --query oidcIssuerProfile.issuerUrl -o tsv |
The issuer is compared byte for byte against the iss claim, so paste it whole. AKS publishes its
issuer with a trailing slash, and that slash is part of the value.
GKE needs no command. Its issuer is
https://container.googleapis.com/v1/projects/<project>/locations/<location>/clusters/<cluster>, and
every GKE cluster serves it by default.
OKE asks for the region and the OpenIDConnectDiscoveryKey that oci ce cluster get prints, plus the
realm under Advanced, which defaults to Commercial (OC1). Its discovery document is served from object
storage rather than the API server, so it is public whether or not the cluster is. Only an enhanced,
VCN-native cluster with discovery enabled publishes one.
Project a token into the pod
The token your cluster mounts by default is addressed to the cluster, so penv asks for one of its own through a projected volume.
volumes:
- name: penv-token
projected:
sources:
- serviceAccountToken:
path: token
# Ask for this audience and no other. A token requested for two audiences fails every exchange.
audience: <ORG_ID>
expirationSeconds: 3600
containers:
- name: api
volumeMounts:
# Without this the volume above exists and no container can read it.
- name: penv-token
mountPath: /var/run/secrets/penv
readOnly: trueOn EKS, do not reuse AWS_WEB_IDENTITY_TOKEN_FILE. That token is addressed to sts.amazonaws.com and
its audience cannot be changed, so a projected volume of your own is the only path. The same holds for
AZURE_FEDERATED_TOKEN_FILE on AKS, whose audience is Microsoft's.
Exchange it and run
T=$(cat /var/run/secrets/penv/token)
C=$(curl -sS "https://penv.cloud/api/v1/auth/oidc" \
-H 'content-type: application/json' \
-d "{\"token\":\"$T\"}" | jq -r .credential)
PENV_TOKEN="$C" penv run -- node server.jsThe credential lives fifteen minutes. Your process lives as long as it likes, because penv run reads
the environment once and then hands the values to the child.
The console prints this snippet ending in penv pull instead. Use penv run where you can, because it
puts the values in one child process and writes nothing to disk. Use penv pull where the tool reads a
file and nothing else.
A pod has no keychain, so penv keeps no local copy and every start asks the server. See run in a container for what that means for your probes.
The other route
Where your cluster already runs External Secrets Operator, you do not need penv inside the container at
all. Its webhook provider reads the whole environment from
GET /api/v1/bulk-read-secrets and writes a Kubernetes Secret, and your pod reads that the way it
reads any other one. penv ships no Kubernetes sync adapter of its own, because that operator already
does the job.
| Route | penv at runtime | Rotation reaches the pod |
|---|---|---|
penv run in the container | Yes, one call per start | On the next start |
| External Secrets Operator | No | On the operator's own refresh interval |
Do it in order
- Read your cluster issuer with the command for your platform, or the discovery key on OKE.
- Open Machine Identities, then Connect a Platform, and pick your cluster's entry.
- Fill in what that entry asks for, along with the namespace and the service account, and note the workspace id it shows.
- Add the projected volume with that workspace id as the audience, and mount it read only.
- Install penv in the image and change the command to
penv run. - Roll the deployment and read the pod log. A
401 unauthorizedmeans the trust does not name this service account.
Run in a container
A container has no keychain, so penv keeps no local copy and asks the server on every start, which changes what your image and your health checks need.
Serverless functions
A function is your code from the first line, so values arrive through the platform's own store, and a sync writes them on Lambda, Cloud Run, Workers, Vercel and Deno Deploy.