Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
# tag points at TODAY) and make rollbacks/incident forensics guesswork.
# Requires every container image to carry a tag, and forbids the ':latest'
Comment thread
devantler marked this conversation as resolved.
# tag specifically. A tag+digest reference (img:1.2.3@sha256:…) passes both
# rules — the digest is what actually pins it.
# checks — the digest is what actually pins it.
#
# Audit ONLY for now: third-party chart images can't be enumerated
# statically, and an Enforce policy that trips on one would wedge its
# HelmRelease mid-upgrade (the enforce-flux-best-practices failure mode).
# Flip rule failureAction to Enforce once prod PolicyReports
# Switch validationActions to Deny once prod PolicyReports
# (`kubectl get polr -A | grep disallow-latest-tag`) show zero violations.
#
# Namespace exclusions mirror validate-pod-security: infrastructure
# namespaces that run third-party system charts by design.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: disallow-latest-tag
annotations:
policies.kyverno.io/minversion: 1.13.0
policies.kyverno.io/minversion: 1.17.0
policies.kyverno.io/title: Disallow Latest Tag
policies.kyverno.io/category: Best Practices
policies.kyverno.io/subject: Pod
Expand All @@ -27,77 +27,41 @@ metadata:
mutable ':latest' tag, so what runs is reproducible and the
signature-verification layers gate immutable references.
spec:
background: true
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
operations:
- CREATE
exclude:
any:
- resources:
namespaces:
- kube-system
- kube-public
- kube-node-lease
- flux-system
- longhorn-system
- local-path-storage
- kubescape
- kubevirt
- cdi
- observability
- velero
validate:
failureAction: Audit
message: >-
An explicit image tag is required — a tagless reference silently
resolves to the mutable ':latest'.
pattern:
spec:
=(ephemeralContainers):
- image: "*:*"
=(initContainers):
- image: "*:*"
containers:
- image: "*:*"
- name: validate-image-tag
match:
any:
- resources:
kinds:
- Pod
operations:
- CREATE
exclude:
any:
- resources:
namespaces:
- kube-system
- kube-public
- kube-node-lease
- flux-system
- longhorn-system
- local-path-storage
- kubescape
- kubevirt
- cdi
- observability
- velero
validate:
failureAction: Audit
message: >-
The mutable ':latest' tag is not allowed — pin a version tag so
rollbacks and signature verification reference immutable images.
pattern:
spec:
=(ephemeralContainers):
- image: "!*:latest"
=(initContainers):
- image: "!*:latest"
containers:
- image: "!*:latest"
validationActions:
- Audit
evaluation:
background:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
matchConditions:
- name: not-infrastructure-namespace
expression: >-
!(object.metadata.?namespace.orValue('') in [
'kube-system', 'kube-public', 'kube-node-lease', 'flux-system',
'longhorn-system', 'local-path-storage', 'kubescape', 'kubevirt',
'cdi', 'observability', 'velero'])
variables:
# Every container kind the Pod declares: regular, init and ephemeral.
- name: images
expression: >-
object.spec.containers.map(c, c.image) +
object.spec.?initContainers.orValue([]).map(c, c.image) +
object.spec.?ephemeralContainers.orValue([]).map(c, c.image)
validations:
# The tag lives in the last path segment: a registry port (host:5000/app)
# is not a tag. A digest (app@sha256:…) pins the image, so it also passes.
- expression: >-
variables.images.all(i, i.contains('@') ||
i.substring(i.lastIndexOf('/') + 1).contains(':'))
message: >-
An explicit image tag is required — a tagless reference silently
resolves to the mutable ':latest'.
- expression: "variables.images.all(i, !i.endsWith(':latest'))"
message: >-
The mutable ':latest' tag is not allowed — pin a version tag so
rollbacks and signature verification reference immutable images.
39 changes: 10 additions & 29 deletions tests/disallow-latest-tag/kyverno-test.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Pins the ClusterPolicy's behaviour before its ValidatingPolicy rewrite (#4996).
# A versioned tag and a tag+digest pass both rules; ':latest' fails only the
# latest-tag rule; a tagless image fails only the tag-required rule; the same
# tagless image in an excluded infrastructure namespace is out of scope.
# Pins the policy's behaviour across its ValidatingPolicy rewrite (#4996).
# A versioned tag, a tag+digest and a digest alone pass, with or without a
# registry port; a registry port is not a tag; ':latest' fails, as does a tagless
# image, in a regular, init or ephemeral container; the same tagless image in an
# excluded infrastructure namespace is out of scope.
apiVersion: cli.kyverno.io/v1alpha1
kind: Test
metadata:
Expand All @@ -12,45 +13,25 @@ resources:
- resources.yaml
results:
- policy: disallow-latest-tag
rule: require-image-tag
resources:
- default/versioned-tag
- default/tag-and-digest
- default/latest-tag
- default/latest-tag-init-container
- default/digest-only
- default/registry-port-versioned-tag
kind: Pod
result: pass
- policy: disallow-latest-tag
rule: require-image-tag
resources:
- default/tagless
- default/tagless-init-container
kind: Pod
result: fail
- policy: disallow-latest-tag
rule: validate-image-tag
resources:
- default/versioned-tag
- default/tag-and-digest
- default/tagless
- default/tagless-init-container
kind: Pod
result: pass
- policy: disallow-latest-tag
rule: validate-image-tag
resources:
- default/latest-tag
- default/latest-tag-init-container
- default/latest-tag-ephemeral-container
- default/tagless-ephemeral-container
- default/registry-port-tagless
kind: Pod
result: fail
- policy: disallow-latest-tag
rule: require-image-tag
resources:
- kube-system/tagless
kind: Pod
result: skip
- policy: disallow-latest-tag
rule: validate-image-tag
resources:
- kube-system/tagless
kind: Pod
Expand Down
56 changes: 56 additions & 0 deletions tests/disallow-latest-tag/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,59 @@ spec:
containers:
- name: app
image: ghcr.io/example/app
---
apiVersion: v1
kind: Pod
metadata:
name: latest-tag-ephemeral-container
namespace: default
spec:
ephemeralContainers:
- name: debug
image: ghcr.io/example/debug:latest
containers:
- name: app
image: ghcr.io/example/app:1.2.3
---
apiVersion: v1
kind: Pod
metadata:
name: tagless-ephemeral-container
namespace: default
spec:
ephemeralContainers:
- name: debug
image: ghcr.io/example/debug
containers:
- name: app
image: ghcr.io/example/app:1.2.3
---
apiVersion: v1
kind: Pod
metadata:
name: registry-port-tagless
namespace: default
spec:
containers:
- name: app
image: registry.example.com:5000/example/app
---
apiVersion: v1
kind: Pod
metadata:
name: registry-port-versioned-tag
namespace: default
spec:
containers:
- name: app
image: registry.example.com:5000/example/app:1.2.3
---
apiVersion: v1
kind: Pod
metadata:
name: digest-only
namespace: default
spec:
containers:
- name: app
image: ghcr.io/example/app@sha256:0000000000000000000000000000000000000000000000000000000000000000
3 changes: 1 addition & 2 deletions tests/policy-failure-actions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"require-baseline-workers-for-hcloud-volumes/require-baseline-worker-affinity": "Enforce",
"require-baseline-workers-for-hcloud-volumes/report-claim-lookup-failure": "Audit",
"audit-privileged-rbac/privileged-rules": "Enforce",
"disallow-latest-tag/require-image-tag": "Audit",
"disallow-latest-tag/validate-image-tag": "Audit",
"disallow-latest-tag": "Audit",
"restrict-github-team-external-identity/teammemberships-external-name-matches-declared-member": "Enforce",
"restrict-github-team-external-identity/teamrepositories-external-name-matches-declared-grant": "Enforce",
"restrict-github-team-external-identity/teams-external-name-is-an-approved-identity": "Enforce",
Expand Down
Loading