mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 12:58:02 +08:00
c8fae88e37
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
452 lines
14 KiB
Markdown
452 lines
14 KiB
Markdown
# OIDC Workload Identity Authentication
|
|
|
|
This document describes how to configure Zot to authenticate workloads using OIDC ID tokens, enabling secret-less authentication for automated workflows.
|
|
|
|
## Overview
|
|
|
|
OIDC Workload Identity authentication allows workloads (e.g., Kubernetes pods, CI/CD pipelines) to authenticate to Zot using OIDC ID tokens instead of static credentials. This is similar to how cloud providers implement "workload identity" features and how Kubernetes handles external OIDC authentication.
|
|
|
|
## Benefits
|
|
|
|
- **Secret-less Authentication**: No need to manage static credentials
|
|
- **Automatic Credential Rotation**: Tokens are short-lived and automatically rotated
|
|
- **Fine-grained Access Control**: Map OIDC claims to Zot identities and groups using CEL expressions
|
|
- **Kubernetes Native**: Works seamlessly with Kubernetes ServiceAccount tokens
|
|
- **Multi-Provider Support**: Configure multiple OIDC issuers for different workload types (e.g. multiple clusters)
|
|
- **Standards-based**: Uses standard OIDC protocols
|
|
|
|
## Configuration
|
|
|
|
### Basic Configuration
|
|
|
|
Add OIDC workload identity configuration to your bearer authentication settings. The `oidc` field accepts an array of provider configurations:
|
|
|
|
```json
|
|
{
|
|
"http": {
|
|
"auth": {
|
|
"bearer": {
|
|
"realm": "zot",
|
|
"service": "zot-service",
|
|
"oidc": [
|
|
{
|
|
"issuer": "https://kubernetes.default.svc.cluster.local",
|
|
"audiences": ["zot"]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Configuration Options
|
|
|
|
- **`issuer`** (required): The OIDC issuer URL. This is the identity provider that signs the tokens.
|
|
- Example: `"https://kubernetes.default.svc.cluster.local"`
|
|
- Example: `"https://token.actions.githubusercontent.com"`
|
|
|
|
- **`audiences`** (required): List of acceptable audiences for the OIDC token. At least one must be specified.
|
|
- Example: `["zot", "https://zot.example.com"]`
|
|
|
|
- **`claimMapping`** (optional): CEL-based configuration for validating and mapping OIDC claims.
|
|
- **`variables`**: List of variables to extract from claims using CEL expressions
|
|
- **`validations`**: List of validation rules with CEL expressions
|
|
- **`username`**: CEL expression to extract the username. Default: `"claims.iss + '/' + claims.sub"`
|
|
- **`groups`**: CEL expression to extract groups. Default: none (no groups extracted)
|
|
|
|
- **`certificateAuthority`** (optional): PEM-encoded CA certificate to validate the OIDC provider's TLS certificate. Useful when the OIDC issuer uses a private CA (e.g., Kubernetes API server with a self-signed certificate). Mutually exclusive with `certificateAuthorityFile`.
|
|
|
|
- **`certificateAuthorityFile`** (optional): Path to a PEM-encoded CA certificate file to validate the OIDC provider's TLS certificate. Mutually exclusive with `certificateAuthority`.
|
|
|
|
- **`skipIssuerVerification`** (optional): Skip issuer verification (for testing only). Default: `false`.
|
|
|
|
### CEL Expressions
|
|
|
|
Zot uses [Common Expression Language (CEL)](https://github.com/google/cel-go) for flexible claim validation and mapping. CEL expressions have access to:
|
|
|
|
- **`claims`**: The OIDC token claims as a map (e.g., `claims.sub`, `claims.email`)
|
|
- **`vars`**: Previously extracted variables (for use in validations and username/groups expressions)
|
|
|
|
#### Example CEL Expressions
|
|
|
|
| Expression | Description |
|
|
|------------|-------------|
|
|
| `claims.sub` | Extract the subject claim |
|
|
| `claims.email` | Extract the email claim |
|
|
| `claims.groups` | Extract the groups claim |
|
|
| `claims['kubernetes.io/serviceaccount/namespace']` | Extract claims with special characters |
|
|
| `claims.repository_owner + '/' + claims.sub` | Concatenate multiple claims |
|
|
| `claims.email.split('@')[0]` | Extract username from email |
|
|
| `claims.org in ['allowed-org-1', 'allowed-org-2']` | Check if org is in allowed list |
|
|
| `claims.email_verified == true` | Validate email is verified |
|
|
|
|
### Complete Example
|
|
|
|
In the example below, the username is mapped from both the issuer and subject
|
|
claims to uniquely identify Kubernetes ServiceAccounts across different clusters.
|
|
Note that `claims.iss + '/' + claims.sub` is the default username mapping if none
|
|
is specified (so the whole `claimMapping` section could be omitted in this example).
|
|
|
|
```json
|
|
{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "/tmp/zot"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "8080",
|
|
"auth": {
|
|
"bearer": {
|
|
"realm": "zot",
|
|
"service": "zot-service",
|
|
"oidc": [
|
|
{
|
|
"issuer": "https://kubernetes.default.svc.cluster.local",
|
|
"audiences": ["zot", "https://zot.example.com"],
|
|
"claimMapping": {
|
|
"username": "claims.iss + '/' + claims.sub"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"accessControl": {
|
|
"repositories": {
|
|
"**": {
|
|
"policies": [
|
|
{
|
|
"users": ["https://kubernetes.default.svc.cluster.local/system:serviceaccount:flux-system:source-controller"],
|
|
"actions": ["read", "create", "update", "delete"]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"log": {
|
|
"level": "info"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Configuration with Custom CA
|
|
|
|
When the OIDC issuer uses a private CA (e.g., Kubernetes API server), you can configure the CA certificate inline or via a file path:
|
|
|
|
```json
|
|
{
|
|
"http": {
|
|
"auth": {
|
|
"bearer": {
|
|
"oidc": [
|
|
{
|
|
"issuer": "https://kubernetes.default.svc.cluster.local",
|
|
"audiences": ["zot"],
|
|
"certificateAuthorityFile": "/etc/zot/k8s-ca.pem"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Alternatively, you can embed the CA certificate directly using `certificateAuthority`:
|
|
|
|
```json
|
|
{
|
|
"http": {
|
|
"auth": {
|
|
"bearer": {
|
|
"oidc": [
|
|
{
|
|
"issuer": "https://kubernetes.default.svc.cluster.local",
|
|
"audiences": ["zot"],
|
|
"certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Advanced Configuration with CEL Validations
|
|
|
|
Use CEL expressions to validate claims and extract complex usernames:
|
|
|
|
```json
|
|
{
|
|
"http": {
|
|
"auth": {
|
|
"bearer": {
|
|
"oidc": [
|
|
{
|
|
"issuer": "https://token.actions.githubusercontent.com",
|
|
"audiences": ["zot"],
|
|
"claimMapping": {
|
|
"variables": [
|
|
{
|
|
"name": "repo",
|
|
"expression": "claims.repository"
|
|
},
|
|
{
|
|
"name": "owner",
|
|
"expression": "claims.repository_owner"
|
|
}
|
|
],
|
|
"validations": [
|
|
{
|
|
"expression": "vars.owner == 'my-org'",
|
|
"message": "only my-org repositories are allowed"
|
|
},
|
|
{
|
|
"expression": "claims.ref.startsWith('refs/heads/')",
|
|
"message": "must be a branch reference"
|
|
}
|
|
],
|
|
"username": "vars.repo",
|
|
"groups": "['github-actions', 'ci']"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Multiple OIDC Providers
|
|
|
|
Configure multiple OIDC providers to support different identity sources. Zot will try each provider in order until one successfully authenticates the token:
|
|
|
|
```json
|
|
{
|
|
"http": {
|
|
"auth": {
|
|
"bearer": {
|
|
"oidc": [
|
|
{
|
|
"issuer": "https://kubernetes.default.svc.cluster.local",
|
|
"audiences": ["zot"],
|
|
"claimMapping": {
|
|
"variables": [
|
|
{
|
|
"name": "ns",
|
|
"expression": "claims['kubernetes.io/serviceaccount/namespace']"
|
|
},
|
|
{
|
|
"name": "sa",
|
|
"expression": "claims['kubernetes.io/serviceaccount/service-account.name']"
|
|
}
|
|
],
|
|
"username": "vars.ns + ':' + vars.sa",
|
|
"groups": "['k8s-workloads']"
|
|
}
|
|
},
|
|
{
|
|
"issuer": "https://token.actions.githubusercontent.com",
|
|
"audiences": ["zot"],
|
|
"claimMapping": {
|
|
"username": "claims.repository",
|
|
"groups": "['github-actions']"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Kubernetes ServiceAccount Tokens
|
|
|
|
When running in Kubernetes, workloads can use their ServiceAccount tokens to authenticate:
|
|
|
|
```bash
|
|
# Get the ServiceAccount token
|
|
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
|
|
|
# Use it to authenticate to Zot
|
|
curl -H "Authorization: Bearer $TOKEN" https://zot.example.com/v2/_catalog
|
|
```
|
|
|
|
### Flux Integration
|
|
|
|
Flux can use Kubernetes ServiceAccount tokens to authenticate to Zot without secrets:
|
|
|
|
```yaml
|
|
apiVersion: source.toolkit.fluxcd.io/v1
|
|
kind: OCIRepository
|
|
metadata:
|
|
name: zot-repo
|
|
namespace: flux-system
|
|
spec:
|
|
url: oci://zot.example.com/v2/manifests
|
|
credentials: ServiceAccountToken
|
|
serviceAccountName: my-tenant-sa # optional. if omitted, defaults to the source-controller ServiceAccount
|
|
```
|
|
|
|
Note: The configuration above is currently a proposal from the Flux maintainers
|
|
and may change until officially released. For more details, see this
|
|
[RFC](https://github.com/fluxcd/flux2/issues/5681).
|
|
|
|
### GitHub Actions
|
|
|
|
GitHub Actions can use OIDC tokens to authenticate:
|
|
|
|
```yaml
|
|
- name: Login to Zot
|
|
run: |
|
|
TOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
|
|
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=zot" | jq -r .value)
|
|
echo $TOKEN | docker login -u oauth --password-stdin zot.example.com
|
|
```
|
|
|
|
## Token Claims
|
|
|
|
### Required Claims
|
|
|
|
- **`iss`**: Issuer URL (must match configured issuer)
|
|
- **`aud`**: Audience (must match one of the configured audiences)
|
|
- **`sub`**: Subject (used as username by default)
|
|
- **`exp`**: Expiration time
|
|
- **`iat`**: Issued at time
|
|
|
|
### Optional Claims
|
|
|
|
- **`groups`**: Array of group names for authorization (requires CEL `groups` expression)
|
|
- **`preferred_username`**: Can be used as username with CEL expression
|
|
- **`email`**: Can be used as username with CEL expression
|
|
- **`name`**: Can be used as username with CEL expression
|
|
|
|
### Example Token Payload
|
|
|
|
```json
|
|
{
|
|
"iss": "https://kubernetes.default.svc.cluster.local",
|
|
"aud": ["zot"],
|
|
"sub": "system:serviceaccount:flux-system:source-controller",
|
|
"exp": 1705258800,
|
|
"iat": 1705255200,
|
|
"kubernetes.io/serviceaccount/namespace": "flux-system",
|
|
"kubernetes.io/serviceaccount/service-account.name": "source-controller"
|
|
}
|
|
```
|
|
|
|
## Access Control
|
|
|
|
Use Zot's access control policies to grant permissions based on the OIDC identity:
|
|
|
|
```json
|
|
{
|
|
"accessControl": {
|
|
"repositories": {
|
|
"app/**": {
|
|
"policies": [
|
|
{
|
|
"users": ["system:serviceaccount:prod:app-controller"],
|
|
"actions": ["read", "create", "update"]
|
|
}
|
|
]
|
|
},
|
|
"**": {
|
|
"policies": [
|
|
{
|
|
"users": ["system:serviceaccount:default:admin"],
|
|
"actions": ["read", "create", "update", "delete"]
|
|
}
|
|
],
|
|
"defaultPolicy": ["read"]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Compatibility
|
|
|
|
### Traditional Bearer Authentication
|
|
|
|
OIDC workload identity can coexist with traditional bearer authentication. If both are configured, Zot will try OIDC authentication first, then fall back to traditional bearer token authentication:
|
|
|
|
```json
|
|
{
|
|
"http": {
|
|
"auth": {
|
|
"bearer": {
|
|
"realm": "https://auth.myreg.io/auth/token",
|
|
"service": "myauth",
|
|
"cert": "/etc/zot/auth.crt",
|
|
"oidc": [
|
|
{
|
|
"issuer": "https://kubernetes.default.svc.cluster.local",
|
|
"audiences": ["zot"]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Other Authentication Methods
|
|
|
|
OIDC workload identity is only available with bearer authentication. For other authentication methods (htpasswd, LDAP, OAuth2 for humans), continue using the existing configuration options.
|
|
|
|
## Troubleshooting
|
|
|
|
### Enable Debug Logging
|
|
|
|
Set log level to `debug` to see detailed authentication logs:
|
|
|
|
```json
|
|
{
|
|
"log": {
|
|
"level": "debug"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Common Issues
|
|
|
|
1. **Token verification failed**: Check that the issuer URL is correct and reachable from Zot.
|
|
|
|
2. **Audience not accepted**: Ensure the token's `aud` claim matches one of the configured audiences.
|
|
|
|
3. **Token expired**: OIDC tokens are typically short-lived. Ensure your workload is obtaining fresh tokens.
|
|
|
|
4. **CEL expression error**: Check the CEL expression syntax. Use `claims.field` for simple fields or `claims['field-name']` for fields with special characters.
|
|
|
|
5. **Validation failed**: Check that your token claims satisfy all configured validation expressions.
|
|
|
|
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. If the issuer uses a private CA, configure `certificateAuthority` or `certificateAuthorityFile` for the corresponding OIDC provider.
|
|
|
|
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.
|
|
|
|
## Security Considerations
|
|
|
|
1. **Token Expiration**: Always use short-lived tokens (typically 1 hour or less).
|
|
|
|
2. **Audience Validation**: Always specify audiences to prevent token reuse across services.
|
|
|
|
3. **TLS**: Use TLS for all communication to protect tokens in transit.
|
|
|
|
4. **Issuer Verification**: Never disable issuer verification in production.
|
|
|
|
5. **Access Control**: Always configure access control policies to limit what authenticated workloads can do.
|
|
|
|
6. **CEL Validations**: Use CEL validations to enforce additional security constraints (e.g., require email verification, restrict to specific organizations).
|
|
|
|
## References
|
|
|
|
- [OIDC Specification](https://openid.net/specs/openid-connect-core-1_0.html)
|
|
- [CEL Language Definition](https://github.com/google/cel-spec)
|
|
- [Kubernetes OIDC Authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens)
|
|
- [Flux Workload Identity RFC](https://github.com/fluxcd/flux2/tree/main/rfcs/0010-multi-tenant-workload-identity)
|
|
- [GitHub Actions OIDC](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|