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
22 changes: 18 additions & 4 deletions internal/webhook/v1alpha1/imageregistry_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,22 @@ func validateImageRegistry(obj *sreportalv1alpha1.ImageRegistry) error {
if e.MutatedImage == "" {
return fmt.Errorf("spec.images[%d].mutatedImage is required", i)
}
if err := assertImageHostMatches(e.MutatedImage, obj.Spec.Host); err != nil {
return fmt.Errorf("spec.images[%d].mutatedImage: %w", i, err)
}
// spec.host must match the *lookup target* — the registry the controller
// will query for tags. That is OriginalImage when present, else
// MutatedImage. MutatedImage may diverge legitimately when an admission
// controller rewrites references to a pull-through cache or mirror.
switch e.ChangeType {
case "none", "mutated":
case "none":
if e.OriginalImage == "" {
return fmt.Errorf("spec.images[%d]: changeType=%q requires originalImage", i, e.ChangeType)
}
if err := assertImageHostMatches(e.OriginalImage, obj.Spec.Host); err != nil {
return fmt.Errorf("spec.images[%d].originalImage: %w", i, err)
}
if err := assertImageHostMatches(e.MutatedImage, obj.Spec.Host); err != nil {
return fmt.Errorf("spec.images[%d].mutatedImage: %w", i, err)
}
case "mutated":
if e.OriginalImage == "" {
return fmt.Errorf("spec.images[%d]: changeType=%q requires originalImage", i, e.ChangeType)
}
Expand All @@ -111,6 +122,9 @@ func validateImageRegistry(obj *sreportalv1alpha1.ImageRegistry) error {
if e.OriginalImage != "" {
return fmt.Errorf("spec.images[%d]: changeType=injected forbids originalImage", i)
}
if err := assertImageHostMatches(e.MutatedImage, obj.Spec.Host); err != nil {
return fmt.Errorf("spec.images[%d].mutatedImage: %w", i, err)
}
default:
return fmt.Errorf("spec.images[%d].changeType %q must be one of none|mutated|injected", i, e.ChangeType)
}
Expand Down
32 changes: 27 additions & 5 deletions internal/webhook/v1alpha1/imageregistry_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,28 +202,50 @@ func TestImageRegistryValidate_HostDenylist(t *testing.T) {
}
}

func TestImageRegistryValidate_HostCoherence_MutatedMismatch(t *testing.T) {
// TestImageRegistryValidate_HostCoherence_MutatedDivergesAllowed verifies that
// for ChangeType=mutated, MutatedImage may live in a different registry than
// spec.host. Rationale: the lookup target (used by the controller to query
// versions) is OriginalImage. MutatedImage is the rewritten reference produced
// by an admission controller (pull-through cache, registry mirror) and is
// allowed to diverge.
func TestImageRegistryValidate_HostCoherence_MutatedDivergesAllowed(t *testing.T) {
t.Parallel()
ir := mkIR(t, func(r *sreportalv1alpha1.ImageRegistry) {
r.Spec.Host = "ghcr.io"
r.Spec.Host = tHostGHCR
r.Spec.Images = []sreportalv1alpha1.ImageRegistrySpecEntry{{
Key: "k",
OriginalImage: "ghcr.io/myorg/myapp:1.0.0",
MutatedImage: tImgNginxDocker,
MutatedImage: tImgNginxDocker, // docker.io — mirrored/rewritten
ChangeType: tChangeTypeMut,
}}
})
v := &ImageRegistryCustomValidator{}
if _, err := v.ValidateCreate(context.Background(), ir); err != nil {
t.Fatalf("expected MutatedImage host divergence to be accepted for changeType=mutated, got %v", err)
}
}

func TestImageRegistryValidate_HostCoherence_InjectedMutatedMismatch(t *testing.T) {
t.Parallel()
ir := mkIR(t, func(r *sreportalv1alpha1.ImageRegistry) {
r.Spec.Host = tHostGHCR
r.Spec.Images = []sreportalv1alpha1.ImageRegistrySpecEntry{{
Key: "k",
MutatedImage: tImgNginxDocker, // docker.io — must match spec.host for injected
ChangeType: tChangeTypeInj,
}}
})
v := &ImageRegistryCustomValidator{}
_, err := v.ValidateCreate(context.Background(), ir)
if err == nil || !strings.Contains(err.Error(), "does not match spec.host") {
t.Fatalf("expected host mismatch error, got %v", err)
t.Fatalf("expected host mismatch error for injected mutatedImage, got %v", err)
}
}

func TestImageRegistryValidate_HostCoherence_OriginalMismatch(t *testing.T) {
t.Parallel()
ir := mkIR(t, func(r *sreportalv1alpha1.ImageRegistry) {
r.Spec.Host = "ghcr.io"
r.Spec.Host = tHostGHCR
r.Spec.Images = []sreportalv1alpha1.ImageRegistrySpecEntry{{
Key: "k",
OriginalImage: tImgNginxDocker,
Expand Down
1 change: 1 addition & 0 deletions internal/webhook/v1alpha1/testconst_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const (
tPortalMain = "main"
tKindDeployment = "deployment"
tImgNginxDocker = "docker.io/library/nginx:1.25.0"
tHostGHCR = "ghcr.io"
tChangeTypeNone = "none"
tChangeTypeMut = "mutated"
tChangeTypeInj = "injected"
Expand Down