@@ -3,7 +3,6 @@ package v1alpha1
33import (
44 "context"
55 "fmt"
6- "time"
76
87 apierrors "k8s.io/apimachinery/pkg/api/errors"
98 "k8s.io/apimachinery/pkg/runtime"
@@ -38,10 +37,6 @@ func (w *ClusterGKMCache) SetupWebhookWithManager(mgr ctrl.Manager) error {
3837// +kubebuilder:webhook:path=/validate-gkm-io-v1alpha1-clustergkmcache,mutating=false,failurePolicy=fail,sideEffects=None,groups=gkm.io,resources=clustergkmcaches,verbs=create;update,versions=v1alpha1,name=vclustergkmcache.kb.io,admissionReviewVersions=v1
3938
4039// Default implements the mutating webhook logic for defaulting.
41- // The mutating webhook writes both the resolved digest and a
42- // gkm.io/mutationSig that’s bound to the current AdmissionRequest UID + image
43- // + digest. The validating webhooks only accept the digest if that signature
44- // is valid, which guarantees the digest came from the mutator (not the user).
4540func (w * ClusterGKMCache ) Default (ctx context.Context , obj runtime.Object ) error {
4641 clustergkmcacheLog .V (1 ).Info ("Mutating Webhook called" , "object" , obj )
4742
@@ -60,18 +55,11 @@ func (w *ClusterGKMCache) Default(ctx context.Context, obj runtime.Object) error
6055 return nil
6156 }
6257
63- // Resolve & verify image -> digest
64- cctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
65- defer cancel ()
66-
67- clustergkmcacheLog .V (1 ).Info ("Verifying image signature" , "image" , cache .Spec .Image )
68- digest , err := verifyImageSignature (cctx , cache .Spec .Image )
69- if err != nil {
70- clustergkmcacheLog .Error (err , "failed to verify image or resolve digest" )
71- return apierrors .NewBadRequest (fmt .Sprintf (
72- "image signature verification failed for '%s': %s" ,
73- cache .Spec .Image , err .Error (),
74- ))
58+ // First check if the image already contains a digest (e.g., from Kyverno mutation)
59+ var digest string
60+ if extractedDigest := extractDigestFromImage (cache .Spec .Image ); extractedDigest != "" {
61+ clustergkmcacheLog .Info ("Image already contains digest (likely from Kyverno)" , "image" , cache .Spec .Image , "digest" , extractedDigest )
62+ digest = extractedDigest
7563 }
7664 resolvedDigest , digestFound := cache .Annotations [utils .GMKCacheAnnotationResolvedDigest ]
7765 if digestFound {
@@ -82,24 +70,6 @@ func (w *ClusterGKMCache) Default(ctx context.Context, obj runtime.Object) error
8270 }
8371 cache .Annotations [utils .GMKCacheAnnotationResolvedDigest ] = digest
8472
85- // Bind a mutation signature to THIS AdmissionRequest UID
86- req , err := admission .RequestFromContext (ctx )
87- if err != nil {
88- return apierrors .NewBadRequest ("unable to read admission request from context" )
89- }
90- secret , err := mutationKeyFromEnv ()
91- if err != nil {
92- return apierrors .NewBadRequest (err .Error ())
93- }
94- sig , err := signMutation (secret , "" , cache .Spec .Image , digest )
95- if err != nil {
96- return apierrors .NewBadRequest (fmt .Sprintf ("failed to sign mutation: %v" , err ))
97- }
98- cache .Annotations [utils .GMKCacheAnnotationMutationSig ] = sig
99-
100- // Audit for convenience (not part of trust)
101- cache .Annotations [utils .GMKCacheAnnotationLastMutatedBy ] = req .UserInfo .Username
102-
10373 clustergkmcacheLog .Info ("added/updated resolvedDigest" , "image" , cache .Spec .Image , "digest" , digest )
10474 return nil
10575}
@@ -115,36 +85,12 @@ func (w *ClusterGKMCache) ValidateCreate(ctx context.Context, obj runtime.Object
11585 return nil , fmt .Errorf ("spec.image must be set" )
11686 }
11787
118- // The validator sees the mutated object.
119- // If resolvedDigest is present, it must carry a valid mutationSig for THIS request.
120- digest := cache .Annotations [utils .GMKCacheAnnotationResolvedDigest ]
121- sig := cache .Annotations [utils .GMKCacheAnnotationMutationSig ]
122-
123- if digest != "" {
124- secret , err := mutationKeyFromEnv ()
125- if err != nil {
126- return nil , fmt .Errorf ("%s" , err .Error ())
127- }
128- if ! verifyMutation (secret , "" , cache .Spec .Image , digest , sig ) {
129- return nil , fmt .Errorf ("%s present but missing/invalid %s; digest must be set only by the mutating webhook" ,
130- utils .GMKCacheAnnotationResolvedDigest , utils .GMKCacheAnnotationMutationSig )
131- }
132- }
133-
134- // Defense in depth
135- // Recompute digest from the image (same logic used by mutator).
136- // The mutator adds the gkm.io/resolvedDigest annotation
137- // If we just check it exists then the validator will fail.
138- // We just recompute the digest and compare it. If it's OK
139- // we accept the CR object.
140- digest , err := verifyImageSignature (ctx , cache .Spec .Image )
141- if err != nil {
142- return nil , fmt .Errorf ("image signature verification failed: %w" , err )
88+ if _ , exists := cache .Annotations [utils .GMKCacheAnnotationResolvedDigest ]; ! exists {
89+ return nil , fmt .Errorf ("%s must be set by mutating webhook" , utils .GMKCacheAnnotationResolvedDigest )
14390 }
14491
145- ann := cache .Annotations ["gkm.io/resolvedDigest" ]
146- if ann == "" || ann != digest {
147- return nil , fmt .Errorf ("gkm.io/resolvedDigest mismatch - this is not the digest of the verified image" )
92+ if _ , exists := cache .Annotations [utils .KyvernoVerifyImagesAnnotation ]; ! exists {
93+ return nil , fmt .Errorf ("%s must be set by kyverno" , utils .KyvernoVerifyImagesAnnotation )
14894 }
14995
15096 return nil , nil
@@ -164,7 +110,6 @@ func (w *ClusterGKMCache) ValidateUpdate(_ context.Context, oldObj, newObj runti
164110
165111 oldDigest := oldCache .Annotations [utils .GMKCacheAnnotationResolvedDigest ]
166112 newDigest := newCache .Annotations [utils .GMKCacheAnnotationResolvedDigest ]
167- newSig := newCache .Annotations [utils .GMKCacheAnnotationMutationSig ]
168113
169114 // If image didn't change, digest must not change.
170115 if oldImg == newImg {
@@ -178,18 +123,10 @@ func (w *ClusterGKMCache) ValidateUpdate(_ context.Context, oldObj, newObj runti
178123 if newImg == "" {
179124 return nil , fmt .Errorf ("spec.image must be set" )
180125 }
181- if newDigest == "" || newSig == "" {
126+ if newDigest == "" {
182127 return nil , fmt .Errorf ("%s must be set by mutating webhook when spec.image changes" , utils .GMKCacheAnnotationResolvedDigest )
183128 }
184129
185- secret , err := mutationKeyFromEnv ()
186- if err != nil {
187- return nil , fmt .Errorf ("%s" , err .Error ())
188- }
189- if ! verifyMutation (secret , "" , newImg , newDigest , newSig ) {
190- return nil , fmt .Errorf ("invalid %s for updated image; digest must be set only by the mutating webhook" , utils .GMKCacheAnnotationMutationSig )
191- }
192-
193130 return nil , nil
194131}
195132
0 commit comments