https://github.com/eric-carlsson/pod-image-policy
A Kubernetes admission controller that validates and mutates container image references in pods.
https://github.com/eric-carlsson/pod-image-policy
admission container image kubernetes mutation validation webhook
Last synced: 6 months ago
JSON representation
A Kubernetes admission controller that validates and mutates container image references in pods.
- Host: GitHub
- URL: https://github.com/eric-carlsson/pod-image-policy
- Owner: eric-carlsson
- License: apache-2.0
- Created: 2025-12-20T23:55:03.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2026-01-28T10:07:18.000Z (6 months ago)
- Last Synced: 2026-01-28T10:21:37.441Z (6 months ago)
- Topics: admission, container, image, kubernetes, mutation, validation, webhook
- Language: Go
- Homepage:
- Size: 94.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# pod-image-policy
A Kubernetes admission controller that validates and mutates container image references in pods.
## Features
- Validate image segments with allow/deny/warn actions and custom messages.
- Rewrite image segments using regex capture groups and replacement templates with optional messages.
### Examples
- Reject images with `latest` tag:
```yaml
validate:
rules:
- match:
tag: "latest"
action: deny
message: "'latest' image tags are not allowed"
```
- Warn when using release candidates:
```yaml
validate:
rules:
- match:
tag: ".*-rc.*"
action: warn
message: "prefer using stable releases over release candidates"
```
- Rewrite all public Docker Hub images to use a private mirror: `docker.io/library/` (or ``) → `registry.private/mirror/library/`:
```yaml
mutate:
rules:
- match:
registry: "docker\.io"
repository: "library/(.*)"
replace:
registry: "registry.private"
repository: "mirror/library/${1}"
```
- Rewrite specific images that have been moved to new repositories: `registry.io/team/app:v1` → `registry.io/project/app:v1`:
```yaml
mutate:
rules:
- match:
registry: "registry\.io"
repository: "team/(.*)"
replace:
repository: "project/${1}"
message: "'team/' repositories have been moved to 'project/'"
```
## Deploy
The Helm chart is available as an OCI artifact at `oci://ghcr.io/eric-carlsson/charts/pod-image-policy`.
A TLS certificate is required to enable communication between the API server and the webhook. By default, the chart is configured with a [cert-manager](https://cert-manager.io/docs/) integration that automatically creates a self-signed issuer and TLS certificate. This can be disabled if you want to bring your own TLS certificate.
### With cert-manager
Deploy the Helm chart with default values. This enables the cert-manager integration and creates a self-signed issuer:
```sh
helm upgrade pod-image-policy oci://ghcr.io/eric-carlsson/charts/pod-image-policy \
--install \
--namespace pod-image-policy \
--create-namespace
```
To use an existing cert-manager issuer instead of the self-signed one:
```sh
helm upgrade pod-image-policy oci://ghcr.io/eric-carlsson/charts/pod-image-policy \
--install \
--namespace pod-image-policy \
--create-namespace \
--set certManager.createSelfSignedIssuer=false \
--set certManager.issuerRef.name=my-issuer \
--set certManager.issuerRef.kind=ClusterIssuer
```
### Without cert-manager
If you prefer to manage certificates manually without cert-manager:
1. Create namespace and TLS secret:
```sh
kubectl create namespace pod-image-policy
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
-keyout /tmp/tls.key -out /tmp/tls.crt \
-subj "/CN=pod-image-policy.pod-image-policy.svc" \
-addext "subjectAltName=DNS:pod-image-policy.pod-image-policy.svc,DNS:pod-image-policy.pod-image-policy.svc.cluster.local"
kubectl create secret tls pod-image-policy-tls \
--cert=/tmp/tls.crt --key=/tmp/tls.key -n pod-image-policy
```
2. Deploy the Helm chart:
```sh
helm upgrade pod-image-policy oci://ghcr.io/eric-carlsson/charts/pod-image-policy \
--install \
--namespace pod-image-policy \
--set certManager.enabled=false \
--set webhooks.mutating.caBundle=$(cat /tmp/tls.crt | base64 | tr -d '\n') \
--set webhooks.validating.caBundle=$(cat /tmp/tls.crt | base64 | tr -d '\n') \
--set-json 'volumes=[{"name":"tls","secret":{"secretName":"pod-image-policy-tls"}}]' \
--set-json 'volumeMounts=[{"name":"tls","mountPath":"/tls","readOnly":true}]' \
--set-json 'args=["-certFile=/tls/tls.crt","-keyFile=/tls/tls.key"]'
```
### Custom policy configuration
By default, the webhook allows all images without mutations or restrictions. To enforce custom image policies, configure the `policy` value with your desired validation and mutation rules.
`custom-values.yaml`:
```yaml
policy:
mutate:
rules:
- match:
registry: "docker\.io"
repository: "library/(.*)"
replace:
registry: "registry.internal"
repository: "mirror/library/${1}"
message: "Image rewritten to use internal mirror"
validate:
rules:
- match:
tag: "latest"
action: deny
message: "'latest' tags are not allowed"
```
Deploy:
```sh
helm upgrade pod-image-policy oci://ghcr.io/eric-carlsson/charts/pod-image-policy \
--install \
--namespace pod-image-policy \
--create-namespace \
--values custom-values.yaml
```
## Policy reference
The admission controller is configured using a policy. The policy supports both validation and mutation actions.
### Policy schema
```yaml
mutate:
rules:
- match: { }
replace: { }
message: string # optional
validate:
rules:
- match: { }
action: allow|warn|deny
message: string # optional
```
### Match fields
| Field | Description |
| ------------ | ----------------------- |
| `registry` | Image registry hostname |
| `repository` | Repository path |
| `tag` | Image tag |
| `digest` | Image digest |
All fields are optional regex patterns. Omitted fields match any value.
**Pattern anchoring:**
- Patterns without `^` or `$` are auto-anchored: `"latest"` → `"^latest$"`
- Explicit anchors allow partial matching: `"^v.*"` matches version prefixes
- Include any anchor to disable auto-anchoring
### Replace fields
| Field | Description |
| ------------ | -------------------- |
| `registry` | New registry value |
| `repository` | New repository value |
| `tag` | New tag value |
| `digest` | New digest value |
Use `${1}`, `${2}`, etc. to reference capture groups from match patterns.
### Validation actions
| Action | Behavior |
| ------- | -------------------------------------------------- |
| `allow` | Image permitted (no message shown) |
| `warn` | Image permitted with warning in admission response |
| `deny` | Image rejected; pod creation fails |
**Evaluation:**
- **Mutate**: First matching rule wins per image
- **Validate**: All matching rules evaluated; any `deny` rejects the image immediately
- Default action when no rules match: `allow`