Skip to content

Commit 6ab1419

Browse files
reedcortclaude
andcommitted
Add CEL validation for registry entries in image config
Invalid registry entries (e.g., with tags like ":latest" or digests like "@sha256:...") in registrySources fields generate an invalid /etc/containers/policy.json, causing CRI-O to fail and nodes to silently not join the cluster. Add per-item CEL validation rules to insecureRegistries, blockedRegistries, and allowedRegistries fields in the RegistrySources struct to reject invalid entries at the API level. Each entry must match a valid registry scope pattern: hostname[:port][/path], optionally with a wildcard prefix (*.). Also adds MaxItems=512 and items MaxLength=512 bounds required by the Kubernetes CEL cost estimator. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1e7cd4b commit 6ab1419

7 files changed

Lines changed: 155 additions & 14 deletions

File tree

config/v1/types_image.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,41 @@ type RegistryLocation struct {
165165
// +kubebuilder:validation:XValidation:rule="has(self.blockedRegistries) ? !has(self.allowedRegistries) : true",message="Only one of blockedRegistries or allowedRegistries may be set"
166166
type RegistrySources struct {
167167
// insecureRegistries are registries which do not have a valid TLS certificates or only support HTTP connections.
168+
// Each entry must be a valid registry scope in the format hostname[:port][/path],
169+
// optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
170+
// Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
171+
// Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
168172
// +optional
169173
// +listType=atomic
174+
// +kubebuilder:validation:MaxItems=512
175+
// +kubebuilder:validation:items:MaxLength=512
176+
// +kubebuilder:validation:items:XValidation:rule="self.matches('^(\\\\*\\\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')",message="each registry must be a valid hostname[:port][/path] without tags or digests"
170177
InsecureRegistries []string `json:"insecureRegistries,omitempty"`
171178
// blockedRegistries cannot be used for image pull and push actions. All other registries are permitted.
179+
// Each entry must be a valid registry scope in the format hostname[:port][/path],
180+
// optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
181+
// Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
182+
// Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
172183
//
173184
// Only one of BlockedRegistries or AllowedRegistries may be set.
174185
// +optional
175186
// +listType=atomic
187+
// +kubebuilder:validation:MaxItems=512
188+
// +kubebuilder:validation:items:MaxLength=512
189+
// +kubebuilder:validation:items:XValidation:rule="self.matches('^(\\\\*\\\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')",message="each registry must be a valid hostname[:port][/path] without tags or digests"
176190
BlockedRegistries []string `json:"blockedRegistries,omitempty"`
177191
// allowedRegistries are the only registries permitted for image pull and push actions. All other registries are denied.
192+
// Each entry must be a valid registry scope in the format hostname[:port][/path],
193+
// optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
194+
// Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
195+
// Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
178196
//
179197
// Only one of BlockedRegistries or AllowedRegistries may be set.
180198
// +optional
181199
// +listType=atomic
200+
// +kubebuilder:validation:MaxItems=512
201+
// +kubebuilder:validation:items:MaxLength=512
202+
// +kubebuilder:validation:items:XValidation:rule="self.matches('^(\\\\*\\\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')",message="each registry must be a valid hostname[:port][/path] without tags or digests"
182203
AllowedRegistries []string `json:"allowedRegistries,omitempty"`
183204
// containerRuntimeSearchRegistries are registries that will be searched when pulling images that do not have fully qualified
184205
// domains in their pull specs. Registries will be searched in the order provided in the list.

config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_images.crd.yaml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,39 @@ spec:
129129
allowedRegistries:
130130
description: |-
131131
allowedRegistries are the only registries permitted for image pull and push actions. All other registries are denied.
132+
Each entry must be a valid registry scope in the format hostname[:port][/path],
133+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
134+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
135+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
132136
133137
Only one of BlockedRegistries or AllowedRegistries may be set.
134138
items:
139+
maxLength: 512
135140
type: string
141+
x-kubernetes-validations:
142+
- message: each registry must be a valid hostname[:port][/path]
143+
without tags or digests
144+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
145+
maxItems: 512
136146
type: array
137147
x-kubernetes-list-type: atomic
138148
blockedRegistries:
139149
description: |-
140150
blockedRegistries cannot be used for image pull and push actions. All other registries are permitted.
151+
Each entry must be a valid registry scope in the format hostname[:port][/path],
152+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
153+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
154+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
141155
142156
Only one of BlockedRegistries or AllowedRegistries may be set.
143157
items:
158+
maxLength: 512
144159
type: string
160+
x-kubernetes-validations:
161+
- message: each registry must be a valid hostname[:port][/path]
162+
without tags or digests
163+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
164+
maxItems: 512
145165
type: array
146166
x-kubernetes-list-type: atomic
147167
containerRuntimeSearchRegistries:
@@ -156,10 +176,20 @@ spec:
156176
type: array
157177
x-kubernetes-list-type: set
158178
insecureRegistries:
159-
description: insecureRegistries are registries which do not have
160-
a valid TLS certificates or only support HTTP connections.
179+
description: |-
180+
insecureRegistries are registries which do not have a valid TLS certificates or only support HTTP connections.
181+
Each entry must be a valid registry scope in the format hostname[:port][/path],
182+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
183+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
184+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
161185
items:
186+
maxLength: 512
162187
type: string
188+
x-kubernetes-validations:
189+
- message: each registry must be a valid hostname[:port][/path]
190+
without tags or digests
191+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
192+
maxItems: 512
163193
type: array
164194
x-kubernetes-list-type: atomic
165195
type: object

config/v1/zz_generated.featuregated-crd-manifests/images.config.openshift.io/AAA_ungated.yaml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,39 @@ spec:
112112
allowedRegistries:
113113
description: |-
114114
allowedRegistries are the only registries permitted for image pull and push actions. All other registries are denied.
115+
Each entry must be a valid registry scope in the format hostname[:port][/path],
116+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
117+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
118+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
115119
116120
Only one of BlockedRegistries or AllowedRegistries may be set.
117121
items:
122+
maxLength: 512
118123
type: string
124+
x-kubernetes-validations:
125+
- message: each registry must be a valid hostname[:port][/path]
126+
without tags or digests
127+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
128+
maxItems: 512
119129
type: array
120130
x-kubernetes-list-type: atomic
121131
blockedRegistries:
122132
description: |-
123133
blockedRegistries cannot be used for image pull and push actions. All other registries are permitted.
134+
Each entry must be a valid registry scope in the format hostname[:port][/path],
135+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
136+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
137+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
124138
125139
Only one of BlockedRegistries or AllowedRegistries may be set.
126140
items:
141+
maxLength: 512
127142
type: string
143+
x-kubernetes-validations:
144+
- message: each registry must be a valid hostname[:port][/path]
145+
without tags or digests
146+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
147+
maxItems: 512
128148
type: array
129149
x-kubernetes-list-type: atomic
130150
containerRuntimeSearchRegistries:
@@ -139,10 +159,20 @@ spec:
139159
type: array
140160
x-kubernetes-list-type: set
141161
insecureRegistries:
142-
description: insecureRegistries are registries which do not have
143-
a valid TLS certificates or only support HTTP connections.
162+
description: |-
163+
insecureRegistries are registries which do not have a valid TLS certificates or only support HTTP connections.
164+
Each entry must be a valid registry scope in the format hostname[:port][/path],
165+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
166+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
167+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
144168
items:
169+
maxLength: 512
145170
type: string
171+
x-kubernetes-validations:
172+
- message: each registry must be a valid hostname[:port][/path]
173+
without tags or digests
174+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
175+
maxItems: 512
146176
type: array
147177
x-kubernetes-list-type: atomic
148178
type: object

config/v1/zz_generated.featuregated-crd-manifests/images.config.openshift.io/ImageStreamImportMode.yaml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,39 @@ spec:
130130
allowedRegistries:
131131
description: |-
132132
allowedRegistries are the only registries permitted for image pull and push actions. All other registries are denied.
133+
Each entry must be a valid registry scope in the format hostname[:port][/path],
134+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
135+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
136+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
133137
134138
Only one of BlockedRegistries or AllowedRegistries may be set.
135139
items:
140+
maxLength: 512
136141
type: string
142+
x-kubernetes-validations:
143+
- message: each registry must be a valid hostname[:port][/path]
144+
without tags or digests
145+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
146+
maxItems: 512
137147
type: array
138148
x-kubernetes-list-type: atomic
139149
blockedRegistries:
140150
description: |-
141151
blockedRegistries cannot be used for image pull and push actions. All other registries are permitted.
152+
Each entry must be a valid registry scope in the format hostname[:port][/path],
153+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
154+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
155+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
142156
143157
Only one of BlockedRegistries or AllowedRegistries may be set.
144158
items:
159+
maxLength: 512
145160
type: string
161+
x-kubernetes-validations:
162+
- message: each registry must be a valid hostname[:port][/path]
163+
without tags or digests
164+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
165+
maxItems: 512
146166
type: array
147167
x-kubernetes-list-type: atomic
148168
containerRuntimeSearchRegistries:
@@ -157,10 +177,20 @@ spec:
157177
type: array
158178
x-kubernetes-list-type: set
159179
insecureRegistries:
160-
description: insecureRegistries are registries which do not have
161-
a valid TLS certificates or only support HTTP connections.
180+
description: |-
181+
insecureRegistries are registries which do not have a valid TLS certificates or only support HTTP connections.
182+
Each entry must be a valid registry scope in the format hostname[:port][/path],
183+
optionally prefixed with "*." for wildcard subdomains (e.g., "*.example.com").
184+
Entries must not include tags (e.g., ":latest") or digests (e.g., "@sha256:...").
185+
Each entry must be at most 512 characters in length and the list may contain at most 512 entries.
162186
items:
187+
maxLength: 512
163188
type: string
189+
x-kubernetes-validations:
190+
- message: each registry must be a valid hostname[:port][/path]
191+
without tags or digests
192+
rule: self.matches('^(\\*\\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?(:[0-9]+)?(/[a-z0-9._-]+)*$')
193+
maxItems: 512
164194
type: array
165195
x-kubernetes-list-type: atomic
166196
type: object

config/v1/zz_generated.swagger_doc_generated.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)