@@ -10,15 +10,16 @@ OIDC Workload Identity authentication allows workloads (e.g., Kubernetes pods, C
1010
1111- ** Secret-less Authentication** : No need to manage static credentials
1212- ** Automatic Credential Rotation** : Tokens are short-lived and automatically rotated
13- - ** Fine-grained Access Control** : Map OIDC claims to Zot identities and groups
13+ - ** Fine-grained Access Control** : Map OIDC claims to Zot identities and groups using CEL expressions
1414- ** Kubernetes Native** : Works seamlessly with Kubernetes ServiceAccount tokens
15+ - ** Multi-Provider Support** : Configure multiple OIDC issuers for different workload types (e.g. multiple clusters)
1516- ** Standards-based** : Uses standard OIDC protocols
1617
1718## Configuration
1819
1920### Basic Configuration
2021
21- Add OIDC workload identity configuration to your bearer authentication settings:
22+ Add OIDC workload identity configuration to your bearer authentication settings. The ` oidc ` field accepts an array of provider configurations :
2223
2324``` json
2425{
@@ -27,10 +28,12 @@ Add OIDC workload identity configuration to your bearer authentication settings:
2728 "bearer" : {
2829 "realm" : " zot" ,
2930 "service" : " zot-service" ,
30- "oidc" : {
31- "issuer" : " https://kubernetes.default.svc.cluster.local" ,
32- "audiences" : [" zot" ]
33- }
31+ "oidc" : [
32+ {
33+ "issuer" : " https://kubernetes.default.svc.cluster.local" ,
34+ "audiences" : [" zot" ]
35+ }
36+ ]
3437 }
3538 }
3639 }
@@ -46,16 +49,41 @@ Add OIDC workload identity configuration to your bearer authentication settings:
4649- ** ` audiences ` ** (required): List of acceptable audiences for the OIDC token. At least one must be specified.
4750 - Example: ` ["zot", "https://zot.example.com"] `
4851
49- - ** ` claimMapping.username ` ** (optional): Which OIDC claim to use as the username. Defaults to ` "sub" ` .
50- - Acceptable values: ` "sub" ` , ` "email" ` , ` "preferred_username" ` , ` "name" ` , or any custom claim
51- - Example: ` "preferred_username" `
52-
53- - ** ` jwksDiscoveryUrl ` ** (optional): Override the JWKS discovery URL. If not provided, it defaults to ` {issuer}/.well-known/openid-configuration ` .
52+ - ** ` claimMapping ` ** (optional): CEL-based configuration for validating and mapping OIDC claims .
53+ - ** ` variables ` ** : List of variables to extract from claims using CEL expressions
54+ - ** ` validations ` ** : List of validation rules with CEL expressions
55+ - ** ` username ` ** : CEL expression to extract the username. Default: ` "claims.iss + '/' + claims.sub" `
56+ - ** ` groups ` ** : CEL expression to extract groups. Default: none (no groups extracted)
5457
5558- ** ` skipIssuerVerification ` ** (optional): Skip issuer verification (for testing only). Default: ` false ` .
5659
60+ ### CEL Expressions
61+
62+ Zot uses [ Common Expression Language (CEL)] ( https://github.com/google/cel-go ) for flexible claim validation and mapping. CEL expressions have access to:
63+
64+ - ** ` claims ` ** : The OIDC token claims as a map (e.g., ` claims.sub ` , ` claims.email ` )
65+ - ** ` vars ` ** : Previously extracted variables (for use in validations and username/groups expressions)
66+
67+ #### Example CEL Expressions
68+
69+ | Expression | Description |
70+ | ------------| -------------|
71+ | ` claims.sub ` | Extract the subject claim |
72+ | ` claims.email ` | Extract the email claim |
73+ | ` claims.groups ` | Extract the groups claim |
74+ | ` claims['kubernetes.io/serviceaccount/namespace'] ` | Extract claims with special characters |
75+ | ` claims.repository_owner + '/' + claims.sub ` | Concatenate multiple claims |
76+ | ` claims.email.split('@')[0] ` | Extract username from email |
77+ | ` claims.org in ['allowed-org-1', 'allowed-org-2'] ` | Check if org is in allowed list |
78+ | ` claims.email_verified == true ` | Validate email is verified |
79+
5780### Complete Example
5881
82+ In the example below, the username is mapped from both the issuer and subject
83+ claims to uniquely identify Kubernetes ServiceAccounts across different clusters.
84+ Note that ` claims.iss + '/' + claims.sub ` is the default username mapping if none
85+ is specified (so the whole ` claimMapping ` section could be omitted in this example).
86+
5987``` json
6088{
6189 "distSpecVersion" : " 1.1.1" ,
@@ -69,21 +97,23 @@ Add OIDC workload identity configuration to your bearer authentication settings:
6997 "bearer" : {
7098 "realm" : " zot" ,
7199 "service" : " zot-service" ,
72- "oidc" : {
73- "issuer" : " https://kubernetes.default.svc.cluster.local" ,
74- "audiences" : [" zot" , " https://zot.example.com" ],
75- "claimMapping" : {
76- "username" : " sub"
100+ "oidc" : [
101+ {
102+ "issuer" : " https://kubernetes.default.svc.cluster.local" ,
103+ "audiences" : [" zot" , " https://zot.example.com" ],
104+ "claimMapping" : {
105+ "username" : " claims.iss + '/' + claims.sub"
106+ }
77107 }
78- }
108+ ]
79109 }
80110 },
81111 "accessControl" : {
82112 "repositories" : {
83113 "**" : {
84114 "policies" : [
85115 {
86- "users" : [" system:serviceaccount:default: flux-controller" ],
116+ "users" : [" https://kubernetes.default.svc.cluster.local/ system:serviceaccount:flux-system:source -controller" ],
87117 "actions" : [" read" , " create" , " update" , " delete" ]
88118 }
89119 ]
@@ -97,6 +127,94 @@ Add OIDC workload identity configuration to your bearer authentication settings:
97127}
98128```
99129
130+ ### Advanced Configuration with CEL Validations
131+
132+ Use CEL expressions to validate claims and extract complex usernames:
133+
134+ ``` json
135+ {
136+ "http" : {
137+ "auth" : {
138+ "bearer" : {
139+ "oidc" : [
140+ {
141+ "issuer" : " https://token.actions.githubusercontent.com" ,
142+ "audiences" : [" zot" ],
143+ "claimMapping" : {
144+ "variables" : [
145+ {
146+ "name" : " repo" ,
147+ "expression" : " claims.repository"
148+ },
149+ {
150+ "name" : " owner" ,
151+ "expression" : " claims.repository_owner"
152+ }
153+ ],
154+ "validations" : [
155+ {
156+ "expression" : " vars.owner == 'my-org'" ,
157+ "message" : " only my-org repositories are allowed"
158+ },
159+ {
160+ "expression" : " claims.ref.startsWith('refs/heads/')" ,
161+ "message" : " must be a branch reference"
162+ }
163+ ],
164+ "username" : " vars.repo" ,
165+ "groups" : " ['github-actions', 'ci']"
166+ }
167+ }
168+ ]
169+ }
170+ }
171+ }
172+ }
173+ ```
174+
175+ ### Multiple OIDC Providers
176+
177+ Configure multiple OIDC providers to support different identity sources. Zot will try each provider in order until one successfully authenticates the token:
178+
179+ ``` json
180+ {
181+ "http" : {
182+ "auth" : {
183+ "bearer" : {
184+ "oidc" : [
185+ {
186+ "issuer" : " https://kubernetes.default.svc.cluster.local" ,
187+ "audiences" : [" zot" ],
188+ "claimMapping" : {
189+ "variables" : [
190+ {
191+ "name" : " ns" ,
192+ "expression" : " claims['kubernetes.io/serviceaccount/namespace']"
193+ },
194+ {
195+ "name" : " sa" ,
196+ "expression" : " claims['kubernetes.io/serviceaccount/service-account.name']"
197+ }
198+ ],
199+ "username" : " vars.ns + ':' + vars.sa" ,
200+ "groups" : " ['k8s-workloads']"
201+ }
202+ },
203+ {
204+ "issuer" : " https://token.actions.githubusercontent.com" ,
205+ "audiences" : [" zot" ],
206+ "claimMapping" : {
207+ "username" : " claims.repository" ,
208+ "groups" : " ['github-actions']"
209+ }
210+ }
211+ ]
212+ }
213+ }
214+ }
215+ }
216+ ```
217+
100218## Usage
101219
102220### Kubernetes ServiceAccount Tokens
@@ -111,22 +229,26 @@ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
111229curl -H " Authorization: Bearer $TOKEN " https://zot.example.com/v2/_catalog
112230```
113231
114- ### Flux CD Integration
232+ ### Flux Integration
115233
116- Flux CD can use OIDC workload identity to authenticate to Zot without storing credentials :
234+ Flux can use Kubernetes ServiceAccount tokens to authenticate to Zot without secrets :
117235
118236``` yaml
119237apiVersion : source.toolkit.fluxcd.io/v1
120- kind : HelmRepository
238+ kind : OCIRepository
121239metadata :
122240 name : zot-repo
123241 namespace : flux-system
124242spec :
125- url : https ://zot.example.com/v2/
126- type : oci
127- provider : generic
243+ url : oci ://zot.example.com/v2/manifests
244+ credentials : ServiceAccountToken
245+ serviceAccountName : my-tenant-sa # optional. if omitted, defaults to the source-controller ServiceAccount
128246` ` `
129247
248+ Note: The configuration above is currently a proposal from the Flux maintainers
249+ and may change until officially released. For more details, see this
250+ [RFC](https://github.com/fluxcd/flux2/issues/5681).
251+
130252### GitHub Actions
131253
132254GitHub Actions can use OIDC tokens to authenticate:
@@ -151,21 +273,22 @@ GitHub Actions can use OIDC tokens to authenticate:
151273
152274# ## Optional Claims
153275
154- - **`groups`**: Array of group names for authorization
155- - **`preferred_username`**: Can be used as username with claim mapping
156- - **`email`**: Can be used as username with claim mapping
157- - **`name`**: Can be used as username with claim mapping
276+ - **`groups`**: Array of group names for authorization (requires CEL `groups` expression)
277+ - **`preferred_username`**: Can be used as username with CEL expression
278+ - **`email`**: Can be used as username with CEL expression
279+ - **`name`**: Can be used as username with CEL expression
158280
159281# ## Example Token Payload
160282
161283` ` ` json
162284{
163285 "iss": "https://kubernetes.default.svc.cluster.local",
164- "aud": "zot",
165- "sub": "system:serviceaccount:default: flux-controller",
286+ "aud": [ "zot"] ,
287+ "sub": "system:serviceaccount:flux-system:source -controller",
166288 "exp": 1705258800,
167289 "iat": 1705255200,
168- "groups": ["system:serviceaccounts", "system:authenticated"]
290+ "kubernetes.io/serviceaccount/namespace": "flux-system",
291+ "kubernetes.io/serviceaccount/service-account.name": "source-controller"
169292}
170293` ` `
171294
@@ -213,10 +336,12 @@ OIDC workload identity can coexist with traditional bearer authentication. If bo
213336 "realm": "https://auth.myreg.io/auth/token",
214337 "service": "myauth",
215338 "cert": "/etc/zot/auth.crt",
216- "oidc": {
217- "issuer": "https://kubernetes.default.svc.cluster.local",
218- "audiences": ["zot"]
219- }
339+ "oidc": [
340+ {
341+ "issuer": "https://kubernetes.default.svc.cluster.local",
342+ "audiences": ["zot"]
343+ }
344+ ]
220345 }
221346 }
222347 }
@@ -249,9 +374,13 @@ Set log level to `debug` to see detailed authentication logs:
249374
2503753. **Token expired** : OIDC tokens are typically short-lived. Ensure your workload is obtaining fresh tokens.
251376
252- 4. **No username found ** : Check the claim mapping configuration and ensure the specified claim exists in the token .
377+ 4. **CEL expression error ** : Check the CEL expression syntax. Use `claims.field` for simple fields or `claims['field-name']` for fields with special characters .
253378
254- 5. **JWKS endpoint not reachable** : Verify network connectivity to the OIDC issuer's JWKS endpoint.
379+ 5. **Validation failed** : Check that your token claims satisfy all configured validation expressions.
380+
381+ 6. **JWKS endpoint not reachable** : Verify network connectivity to the OIDC issuer's JWKS endpoint. Note: Zot lazily initializes the OIDC provider on first authentication, so startup won't fail if the issuer is temporarily unreachable.
382+
383+ 7. **No username found** : Ensure the CEL expression for username evaluates to a non-empty string. Check that the required claims exist in the token.
255384
256385# # Security Considerations
257386
@@ -265,8 +394,12 @@ Set log level to `debug` to see detailed authentication logs:
265394
2663955. **Access Control** : Always configure access control policies to limit what authenticated workloads can do.
267396
397+ 6. **CEL Validations** : Use CEL validations to enforce additional security constraints (e.g., require email verification, restrict to specific organizations).
398+
268399# # References
269400
270401- [OIDC Specification](https://openid.net/specs/openid-connect-core-1_0.html)
402+ - [CEL Language Definition](https://github.com/google/cel-spec)
271403- [Kubernetes OIDC Authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens)
272404- [Flux Workload Identity RFC](https://github.com/fluxcd/flux2/tree/main/rfcs/0010-multi-tenant-workload-identity)
405+ - [GitHub Actions OIDC](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
0 commit comments