Problem
When following AZURE.md to set up Option B (Azure Workload Identity / projected SA token), it is natural to configure the JFrog identity mapping with a sub claim targeting the DaemonSet's own ServiceAccount:
{
"claims": {
"sub": "system:serviceaccount:jfrog:jfrog-provider-sa",
"aud": "api://AzureADTokenExchange",
"iss": "https://<aks-oidc-issuer>/"
}
}
This is intuitive because jfrog-provider-sa is annotated with azure.workload.identity/client-id and JFrogExchange: "true" — the same two annotations that AZURE.md calls out — and it is the SA that appears in all the chart-generated resources.
The result is a silent failure: the DaemonSet self-update works, but all application image pulls return 403 from /access/api/v1/oidc/token.
Root cause
The kubelet credential provider plugin does not use the DaemonSet's SA token when fulfilling an image pull request. It uses the requesting pod's SA token. The sub claim in that token is system:serviceaccount::, which does not match system:serviceaccount:jfrog:jfrog-provider-sa.
There are therefore two separate OIDC token exchanges happening:
-
DaemonSet self-update — uses jfrog-provider-sa token (sub = system:serviceaccount:jfrog:jfrog-provider-sa)
-
Image pull credential provision — uses the application pod's SA token (sub = system:serviceaccount::)
Both go through the same identity mapping. Pinning sub to the DaemonSet SA breaks case 2.
Expected documentation
AZURE.md should state that:
The identity mapping should not include a sub constraint (or use a wildcard), because the plugin submits the application pod's SA token, not its own.
The two token exchanges (self-update vs. image pull) are distinct and serve different purposes.
Optionally: if scoping by sub is desired for security, operators must define two separate identity mappings — one for sub: system:serviceaccount:jfrog:jfrog-provider-sa (self-update) and one without sub (or a namespace wildcard pattern) for application pods.
Environment
Helm chart version: (tested with latest)
Auth mode: Option B — Azure Workload Identity (projected SA token)
Cluster: AKS with OIDC issuer enabled
Problem
When following AZURE.md to set up Option B (Azure Workload Identity / projected SA token), it is natural to configure the JFrog identity mapping with a sub claim targeting the DaemonSet's own ServiceAccount:
This is intuitive because jfrog-provider-sa is annotated with azure.workload.identity/client-id and JFrogExchange: "true" — the same two annotations that AZURE.md calls out — and it is the SA that appears in all the chart-generated resources.
The result is a silent failure: the DaemonSet self-update works, but all application image pulls return 403 from /access/api/v1/oidc/token.
Root cause
The kubelet credential provider plugin does not use the DaemonSet's SA token when fulfilling an image pull request. It uses the requesting pod's SA token. The sub claim in that token is system:serviceaccount::, which does not match system:serviceaccount:jfrog:jfrog-provider-sa.
There are therefore two separate OIDC token exchanges happening:
DaemonSet self-update — uses jfrog-provider-sa token (sub = system:serviceaccount:jfrog:jfrog-provider-sa)
Image pull credential provision — uses the application pod's SA token (sub = system:serviceaccount::)
Both go through the same identity mapping. Pinning sub to the DaemonSet SA breaks case 2.
Expected documentation
AZURE.md should state that:
The identity mapping should not include a sub constraint (or use a wildcard), because the plugin submits the application pod's SA token, not its own.
The two token exchanges (self-update vs. image pull) are distinct and serve different purposes.
Optionally: if scoping by sub is desired for security, operators must define two separate identity mappings — one for sub: system:serviceaccount:jfrog:jfrog-provider-sa (self-update) and one without sub (or a namespace wildcard pattern) for application pods.
Environment
Helm chart version: (tested with latest)
Auth mode: Option B — Azure Workload Identity (projected SA token)
Cluster: AKS with OIDC issuer enabled