A solution that leverages JWT tokens to enable transparent and passwordless container registry authentication and authorization for machine workloads like Kubernetes pods and Github Actions.
This is a proof of concept and is not suitable for production use cases.
There are two main aims here.
- Allow Kubernetes pods and other types of 'machine' workload to pull/push images from a registry without having to define static secrets.
- Enable authorization based on the properties of the workload's identity. For example, allow Kubernetes service accounts within a given namespace to pull from a particular set of repositories.
The goals are accomplished by enabling clients to authenticate to container registries with a JWT token issued by a trusted identity provider.
This PoC provides implementations that enable the solution. These implementations can be split into two categories (server and client) which are expanded on in the following sections.
The container registry server must be capable of accepting a JWT token as a client's credentials, validating the token with a trusted identity provider and performing authorization based on the claims in the token.
As far as I know, the only container registry that currently supports anything like this is Chainguard's registry.
In lieu of native registry support, this PoC implements two methods of strapping JWT authentication and authorization onto an existing registry.
The Distribution registry supports configurable authentication and authorization via a pluggable 'token' server.
The cmd/token-server
application implements a token server
which:
- Accepts a JWT token as a user's password
- Validates and authenticates the token with a trusted identity provider
- Applies authorization based on the token's claims via CEL policies
sequenceDiagram
Client->>Registry: Push/Pull
Registry-->>Client: 401 'WWW-Authenticate: <token-server>'
Client->>Token Server: 'Authorization: Basic <provider>:<jwt>'
Token Server->>Identity Provider: /.well-known/openid-configuration
Identity Provider-->>Token Server: Public Key
Token Server-->>Client: {auth_token: "<token>"}
Client->>Registry: 'Authorization: Bearer <token>'
Registry-->>Client: Response
Most registries don't support pluggable authentication and authorization in the same way that the Distribution registry does.
For these registries, the PoC provides a reverse proxy,
cmd/registry-auth-proxy
that sits in front of a
registry and performs token-based authentication before proxying requests to
the upstream registry.
sequenceDiagram
Client->>Registry Auth Proxy: Push/Pull
Registry Auth Proxy-->>Client: 401 'WWW-Authenticate: <token-server>'
Client->>Token Server: 'Authorization: Basic <provider>:<jwt>'
Token Server->>Identity Provider: /.well-known/openid-configuration
Identity Provider-->>Token Server: Public Key
Token Server-->>Client: {auth_token: "<token>"}
Client->>Registry Auth Proxy: 'Authorization: Bearer <token>'
Registry Auth Proxy->>Upstream Registry: Proxy Request
Upstream Registry-->>Registry Auth Proxy: Response
Registry Auth Proxy-->>Client: Response
Clients that push or pull images from the registry must be configured so that they can fetch a token from their identity provider and then provide that as their password when authenticating with the registry.
There are numerous ways to do this depending on the type of client and where it is running.
This PoC provides two methods, which are outlined below.
The cmd/kube-controller
application runs in the cluster
and enables Kubernetes pods to push and pull images based on their service
account identity.
Refer to the README.md for more information.
The actions/docker-login
GitHub Action logs into a
container registry with a GitHub Actions issued JWT token as the password.
Refer to the README.md for more information.