@@ -41,10 +41,23 @@ import (
41
41
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
42
42
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
43
43
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
44
+ auditinternal "k8s.io/apiserver/pkg/apis/audit"
44
45
webhookutil "k8s.io/apiserver/pkg/util/webhook"
45
46
utiltrace "k8s.io/utils/trace"
46
47
)
47
48
49
+ const (
50
+ // PatchAuditAnnotationPrefix is a prefix for persisting webhook patch in audit annotation.
51
+ // Audit handler decides whether annotation with this prefix should be logged based on audit level.
52
+ // Since mutating webhook patches the request body, audit level must be greater or equal to Request
53
+ // for the annotation to be logged
54
+ PatchAuditAnnotationPrefix = "patch.webhook.admission.k8s.io/"
55
+ // MutationAuditAnnotationPrefix is a prefix for presisting webhook mutation existence in audit annotation.
56
+ MutationAuditAnnotationPrefix = "mutation.webhook.admission.k8s.io/"
57
+ )
58
+
59
+ var encodingjson = json .CaseSensitiveJsonIterator ()
60
+
48
61
type mutatingDispatcher struct {
49
62
cm * webhookutil.ClientManager
50
63
plugin * Plugin
@@ -77,7 +90,7 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
77
90
webhookReinvokeCtx .SetLastWebhookInvocationOutput (attr .GetObject ())
78
91
}()
79
92
var versionedAttr * generic.VersionedAttributes
80
- for _ , hook := range hooks {
93
+ for i , hook := range hooks {
81
94
attrForCheck := attr
82
95
if versionedAttr != nil {
83
96
attrForCheck = versionedAttr
@@ -116,8 +129,11 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
116
129
}
117
130
118
131
t := time .Now ()
119
-
120
- changed , err := a .callAttrMutatingHook (ctx , hook , invocation , versionedAttr , o )
132
+ round := 0
133
+ if reinvokeCtx .IsReinvoke () {
134
+ round = 1
135
+ }
136
+ changed , err := a .callAttrMutatingHook (ctx , hook , invocation , versionedAttr , o , round , i )
121
137
admissionmetrics .Metrics .ObserveWebhook (time .Since (t ), err != nil , versionedAttr .Attributes , "admit" , hook .Name )
122
138
if changed {
123
139
// Patch had changed the object. Prepare to reinvoke all previous webhooks that are eligible for re-invocation.
@@ -162,7 +178,11 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
162
178
163
179
// note that callAttrMutatingHook updates attr
164
180
165
- func (a * mutatingDispatcher ) callAttrMutatingHook (ctx context.Context , h * v1beta1.MutatingWebhook , invocation * generic.WebhookInvocation , attr * generic.VersionedAttributes , o admission.ObjectInterfaces ) (bool , error ) {
181
+ func (a * mutatingDispatcher ) callAttrMutatingHook (ctx context.Context , h * v1beta1.MutatingWebhook , invocation * generic.WebhookInvocation , attr * generic.VersionedAttributes , o admission.ObjectInterfaces , round , idx int ) (bool , error ) {
182
+ configurationName := invocation .Webhook .GetConfigurationName ()
183
+ annotator := newWebhookAnnotator (attr , round , idx , h .Name , configurationName )
184
+ changed := false
185
+ defer func () { annotator .addMutationAnnotation (changed ) }()
166
186
if attr .Attributes .IsDryRun () {
167
187
if h .SideEffects == nil {
168
188
return false , & webhookutil.ErrCallingWebhook {WebhookName : h .Name , Reason : fmt .Errorf ("Webhook SideEffects is nil" )}
@@ -182,7 +202,7 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
182
202
return false , & webhookutil.ErrCallingWebhook {WebhookName : h .Name , Reason : err }
183
203
}
184
204
trace := utiltrace .New ("Call mutating webhook" ,
185
- utiltrace.Field {"configuration" , invocation . Webhook . GetConfigurationName () },
205
+ utiltrace.Field {"configuration" , configurationName },
186
206
utiltrace.Field {"webhook" , h .Name },
187
207
utiltrace.Field {"resource" , attr .GetResource ()},
188
208
utiltrace.Field {"subresource" , attr .GetSubresource ()},
@@ -240,6 +260,7 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
240
260
if err != nil {
241
261
return false , apierrors .NewInternalError (err )
242
262
}
263
+
243
264
if len (patchObj ) == 0 {
244
265
return false , nil
245
266
}
@@ -284,10 +305,103 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
284
305
return false , apierrors .NewInternalError (err )
285
306
}
286
307
287
- changed : = ! apiequality .Semantic .DeepEqual (attr .VersionedObject , newVersionedObject )
308
+ changed = ! apiequality .Semantic .DeepEqual (attr .VersionedObject , newVersionedObject )
288
309
trace .Step ("Patch applied" )
310
+ annotator .addPatchAnnotation (patchObj , result .PatchType )
289
311
attr .Dirty = true
290
312
attr .VersionedObject = newVersionedObject
291
313
o .GetObjectDefaulter ().Default (attr .VersionedObject )
292
314
return changed , nil
293
315
}
316
+
317
+ type webhookAnnotator struct {
318
+ attr * generic.VersionedAttributes
319
+ patchAnnotationKey string
320
+ mutationAnnotationKey string
321
+ webhook string
322
+ configuration string
323
+ }
324
+
325
+ func newWebhookAnnotator (attr * generic.VersionedAttributes , round , idx int , webhook , configuration string ) * webhookAnnotator {
326
+ return & webhookAnnotator {
327
+ attr : attr ,
328
+ patchAnnotationKey : fmt .Sprintf ("%sround_%d_index_%d" , PatchAuditAnnotationPrefix , round , idx ),
329
+ mutationAnnotationKey : fmt .Sprintf ("%sround_%d_index_%d" , MutationAuditAnnotationPrefix , round , idx ),
330
+ webhook : webhook ,
331
+ configuration : configuration ,
332
+ }
333
+ }
334
+
335
+ func (w * webhookAnnotator ) addMutationAnnotation (mutated bool ) {
336
+ if w .attr == nil || w .attr .Attributes == nil {
337
+ return
338
+ }
339
+ value , err := mutationAnnotationValue (w .configuration , w .webhook , mutated )
340
+ if err != nil {
341
+ klog .Warningf ("unexpected error composing mutating webhook annotation: %v" , err )
342
+ return
343
+ }
344
+ if err := w .attr .Attributes .AddAnnotation (w .mutationAnnotationKey , value ); err != nil {
345
+ klog .Warningf ("failed to set mutation annotation for mutating webhook key %s to %s: %v" , w .mutationAnnotationKey , value , err )
346
+ }
347
+ }
348
+
349
+ func (w * webhookAnnotator ) addPatchAnnotation (patch interface {}, patchType admissionv1.PatchType ) {
350
+ if w .attr == nil || w .attr .Attributes == nil {
351
+ return
352
+ }
353
+ var value string
354
+ var err error
355
+ switch patchType {
356
+ case admissionv1 .PatchTypeJSONPatch :
357
+ value , err = jsonPatchAnnotationValue (w .configuration , w .webhook , patch )
358
+ if err != nil {
359
+ klog .Warningf ("unexpected error composing mutating webhook JSON patch annotation: %v" , err )
360
+ return
361
+ }
362
+ default :
363
+ klog .Warningf ("unsupported patch type for mutating webhook annotation: %v" , patchType )
364
+ return
365
+ }
366
+ if err := w .attr .Attributes .AddAnnotationWithLevel (w .patchAnnotationKey , value , auditinternal .LevelRequest ); err != nil {
367
+ // NOTE: we don't log actual patch in kube-apiserver log to avoid potentially
368
+ // leaking information
369
+ klog .Warningf ("failed to set patch annotation for mutating webhook key %s; confugiration name: %s, webhook name: %s" , w .patchAnnotationKey , w .configuration , w .webhook )
370
+ }
371
+ }
372
+
373
+ // MutationAuditAnnotation logs if a webhook invocation mutated the request object
374
+ type MutationAuditAnnotation struct {
375
+ Configuration string `json:"configuration"`
376
+ Webhook string `json:"webhook"`
377
+ Mutated bool `json:"mutated"`
378
+ }
379
+
380
+ // PatchAuditAnnotation logs a patch from a mutating webhook
381
+ type PatchAuditAnnotation struct {
382
+ Configuration string `json:"configuration"`
383
+ Webhook string `json:"webhook"`
384
+ Patch interface {} `json:"patch,omitempty"`
385
+ PatchType string `json:"patchType,omitempty"`
386
+ }
387
+
388
+ func mutationAnnotationValue (configuration , webhook string , mutated bool ) (string , error ) {
389
+ m := MutationAuditAnnotation {
390
+ Configuration : configuration ,
391
+ Webhook : webhook ,
392
+ Mutated : mutated ,
393
+ }
394
+ bytes , err := encodingjson .Marshal (m )
395
+ return string (bytes ), err
396
+ }
397
+
398
+ func jsonPatchAnnotationValue (configuration , webhook string , patch interface {}) (string , error ) {
399
+ p := PatchAuditAnnotation {
400
+ Configuration : configuration ,
401
+ Webhook : webhook ,
402
+ Patch : patch ,
403
+ PatchType : string (admissionv1 .PatchTypeJSONPatch ),
404
+ }
405
+ bytes , err := encodingjson .Marshal (p )
406
+ return string (bytes ), err
407
+ }
0 commit comments