Service Account Token Integration for Image Pulls
Required knowledge for the CKS certification.
Container registry credentials stored in imagePullSecrets are high-value targets: a single compromised Secret exposes every registry it authenticates to, and those credentials often live indefinitely. Starting with Kubernetes v1.33 (alpha) and graduating to Beta (enabled by default) in v1.34, the kubelet credential provider mechanism gained the ability to pass a pod-bound service account token to a credential provider plugin, which the plugin can exchange for short-lived registry credentials — eliminating the need for static, long-lived imagePullSecrets entirely. The feature is on track to graduate to GA in v1.38.
This article explains the mechanism, shows the complete configuration, and covers how to verify that image pulls are using the new identity path.
How It Works
The kubelet credential provider is a plugin system (stable since Kubernetes v1.26) in which the kubelet calls an out-of-process binary to obtain registry credentials for each image pull. The binary communicates with the kubelet through stdin and stdout using versioned Kubernetes API objects.
The service account token integration extends this plugin contract. When tokenAttributes is configured in the plugin's CredentialProviderConfig entry, the kubelet performs the following steps for each image pull:
- Identifies the matching credential provider plugin by comparing the image reference against
matchImages. - Requests a service account token bound to the pod performing the pull, scoped to the audience specified in
tokenAttributes.serviceAccountTokenAudience. - Sends a
CredentialProviderRequestto the plugin via stdin; this request includes the bound token in theserviceAccountTokenfield and the pod's service account annotations inserviceAccountAnnotations. - The plugin exchanges the token with an external identity or registry service (cloud IAM, an OIDC-aware registry, or a custom token exchange endpoint).
- The plugin returns a
CredentialProviderResponsewith credentials; the kubelet uses those credentials to pull the image.
The credential is never stored on disk or in the Kubernetes API. Each pull uses a fresh token bound to that specific pod.
Prerequisites
- Kubernetes v1.34 or later (the feature is Beta and enabled by default; GA is targeted for v1.38).
- A credential provider plugin binary installed on every node that:
- Accepts
serviceAccountTokenandserviceAccountAnnotationsfrom aCredentialProviderRequest. - Knows how to exchange that token for valid registry credentials.
- Accepts
- The registry (or an intermediate auth service) must accept the pod's service account token as proof of identity — for example via OIDC federation.
What is not covered by this feature: The feature gate controls the kubelet side only. The plugin binary itself, and the registry-side token validation, are outside Kubernetes core. This means the feature is available in any Kubernetes v1.34+ cluster (Beta, enabled by default) but is only usable when a compatible plugin is deployed.
Version Compatibility
| Kubernetes version | Stage | Feature gate |
|---|---|---|
| v1.33 | Alpha | KubeletServiceAccountTokenForCredentialProviders=true (off by default) |
| v1.34 | Beta | Enabled by default |
| v1.38 (planned) | Stable | Gate locked to enabled; can no longer be disabled |
On v1.33, you must explicitly enable the feature gate on the kubelet. On v1.34 and later, the gate is enabled by default (no explicit setting required). When the feature graduates to GA in v1.38, the gate is locked to enabled and can no longer be disabled.
1. Install the Credential Provider Plugin
Place the credential provider plugin binary in a directory on every node. The path must be the same on all nodes, and the binary must be executable.
# Example: install to /usr/local/bin
sudo install -m 0755 your-credential-provider /usr/local/bin/your-credential-provider
2. Configure the Kubelet
Set two kubelet flags pointing to the configuration file and the directory containing plugin binaries:
--image-credential-provider-config=/etc/kubernetes/credential-providers/config.yaml
--image-credential-provider-bin-dir=/usr/local/bin
On nodes managed with KubeletConfiguration, add the equivalent fields:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
imageCredentialProviderConfigFile: /etc/kubernetes/credential-providers/config.yaml
imageCredentialProviderBinDir: /usr/local/bin
3. Create the CredentialProviderConfig
The CredentialProviderConfig file lists the plugins and their matching image patterns. Add tokenAttributes to the plugin entry to enable service account token passing:
apiVersion: kubelet.config.k8s.io/v1
kind: CredentialProviderConfig
providers:
- name: your-credential-provider
matchImages:
- "registry.example.com"
- "*.registry.example.com"
defaultCacheDuration: "10m"
apiVersion: credentialprovider.kubelet.k8s.io/v1
tokenAttributes:
serviceAccountTokenAudience: "registry.example.com"
cacheType: "Token"
tokenAttributes fields:
serviceAccountTokenAudience(required): The audience claim embedded in the bound token. The registry or auth service validates this audience when the plugin presents the token.cacheType: Controls how the kubelet caches credentials returned by the plugin."Token"caches on a per-token basis — credentials are considered valid for the lifetime of the specific service account token. Use"Token"when the plugin's returned credential lifetime is tied to the service account token's lifetime.
4. What the Plugin Receives
The kubelet sends a CredentialProviderRequest to the plugin's stdin. When tokenAttributes is configured, the request includes:
{
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
"kind": "CredentialProviderRequest",
"image": "registry.example.com/myapp:v1.2.3",
"serviceAccountToken": "<pod-bound JWT>",
"serviceAccountAnnotations": {
"example.com/role-arn": "arn:aws:iam::123456789012:role/RegistryPullRole"
}
}
The plugin reads the token from serviceAccountToken, exchanges it with the appropriate service, and writes a CredentialProviderResponse to stdout:
{
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
"kind": "CredentialProviderResponse",
"cacheKeyType": "Token",
"auth": {
"registry.example.com": {
"username": "oauth2accesstoken",
"password": "<short-lived-token-from-exchange>"
}
}
}
5. Deploy Pods Without imagePullSecrets
Once the plugin is installed and the kubelet is configured, pods pulling from the matching registry do not need an imagePullSecrets field:
Before: Pod spec requires a static credential Secret.
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
imagePullSecrets:
- name: registry-credentials
containers:
- name: myapp
image: registry.example.com/myapp:v1.2.3
After: The kubelet obtains credentials dynamically via the credential provider plugin using the pod's own service account token.
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
serviceAccountName: myapp-sa
containers:
- name: myapp
image: registry.example.com/myapp:v1.2.3
The pod's service account (myapp-sa) must be federated with the identity provider that the registry trusts. The setup of that federation (IRSA on EKS, Workload Identity on GKE, or a custom OIDC provider) is outside the scope of this article and depends on your registry.
6. Verify the Configuration
After configuring the kubelet and deploying a pod, verify that the image pull succeeds without an imagePullSecrets entry:
# Confirm the pod started without imagePullSecrets
kubectl get pod myapp -o jsonpath='{.spec.imagePullSecrets}'
# Expected: empty output (no imagePullSecrets)
# Confirm the pod is Running
kubectl get pod myapp
# Expected: STATUS Running
Check kubelet logs on the node to confirm the credential provider was invoked:
# On the node — kubelet logs show credential provider calls
journalctl -u kubelet --since "5 minutes ago" | grep -i "credential.*provider\|image.*pull"
Confirm that no imagePullSecrets-holding Secrets exist for this registry in the namespace:
# Audit: list Secrets of type kubernetes.io/dockerconfigjson
kubectl get secrets -n myapp-namespace \
--field-selector type=kubernetes.io/dockerconfigjson \
-o custom-columns='NAME:.metadata.name,NAMESPACE:.metadata.namespace'
Security Properties
| Property | Traditional imagePullSecrets | SA token credential provider |
|---|---|---|
| Credential lifetime | Long-lived until rotated manually | Short-lived; expires with the SA token |
| Credential storage | Stored in a Kubernetes Secret (etcd) | Never stored; obtained dynamically |
| Blast radius if stolen | All workloads using that Secret | The specific pod; token is pod-bound |
| RBAC exposure | Any principal with get secrets access | No Secret to read; no RBAC pivot |
| Registry-side revocation | Requires credential rotation in Secret | Token expiry or SA deletion is sufficient |
| Audit trail | Secret access events | Token issuance + exchange events |
The pod-bound token has a limited audience (serviceAccountTokenAudience) and expires automatically. A compromised node cannot reuse the token for a different pod or a different audience.
Operational Notes
SA token audience must match the registry's expected audience. If the registry validates the aud claim and it does not match serviceAccountTokenAudience, the exchange will fail with an authorization error. Verify the audience configuration with your registry documentation.
Clusters on managed Kubernetes (EKS, GKE, AKS) may lag upstream by one or more minor versions. Confirm that your managed cluster version is v1.34 or later before relying on this feature. On v1.34 and later the gate is enabled by default; on v1.33 it must be explicitly enabled on the kubelet.
The feature covers the kubelet → plugin communication only. Network policies, admission controllers, and registry-side access controls remain separate concerns and are not replaced by this mechanism.
References
This article is based on information from the following official sources:
- Configure a kubelet image credential provider - Kubernetes Documentation
- Kubelet CredentialProvider (v1) - Kubernetes API Reference
- From Secrets to Service Accounts: Kubernetes Image Pulls Evolved - Kubernetes Blog (v1.33 alpha)
- Service Account Token Integration for Image Pulls Graduates to Beta - Kubernetes Blog (v1.34 beta)