Skip to content

Commit bc089a3

Browse files
authored
Merge pull request kubernetes#81857 from jpbetz/admission-conformance
Promote admission webhook e2e tests to conformance
2 parents 3460573 + 68bafe4 commit bc089a3

File tree

2 files changed

+188
-23
lines changed

2 files changed

+188
-23
lines changed

test/conformance/testdata/conformance.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ test/e2e/apimachinery/watch.go: "should be able to start watching from a specifi
2525
test/e2e/apimachinery/watch.go: "should be able to restart watching from the last resource version observed by the previous watch"
2626
test/e2e/apimachinery/watch.go: "should observe an object deletion if it stops meeting the requirements of the selector"
2727
test/e2e/apimachinery/watch.go: "should receive events on concurrent watches in same order"
28+
test/e2e/apimachinery/webhook.go: "should include webhook resources in discovery documents"
29+
test/e2e/apimachinery/webhook.go: "should be able to deny pod and configmap creation"
30+
test/e2e/apimachinery/webhook.go: "should be able to deny attaching pod"
31+
test/e2e/apimachinery/webhook.go: "should be able to deny custom resource creation, update and deletion"
32+
test/e2e/apimachinery/webhook.go: "should unconditionally reject operations on fail closed webhook"
33+
test/e2e/apimachinery/webhook.go: "should mutate configmap"
34+
test/e2e/apimachinery/webhook.go: "should mutate pod and apply defaults after mutation"
35+
test/e2e/apimachinery/webhook.go: "should not be able to mutate or prevent deletion of webhook configuration objects"
36+
test/e2e/apimachinery/webhook.go: "should mutate custom resource"
37+
test/e2e/apimachinery/webhook.go: "should deny crd creation"
38+
test/e2e/apimachinery/webhook.go: "should mutate custom resource with different stored version"
39+
test/e2e/apimachinery/webhook.go: "should mutate custom resource with pruning"
40+
test/e2e/apimachinery/webhook.go: "should honor timeout"
41+
test/e2e/apimachinery/webhook.go: "patching/updating a validating webhook should work"
42+
test/e2e/apimachinery/webhook.go: "patching/updating a mutating webhook should work"
43+
test/e2e/apimachinery/webhook.go: "listing validating webhooks should work"
44+
test/e2e/apimachinery/webhook.go: "listing mutating webhooks should work"
2845
test/e2e/apps/daemon_set.go: "should run and stop simple daemon"
2946
test/e2e/apps/daemon_set.go: "should run and stop complex daemon"
3047
test/e2e/apps/daemon_set.go: "should retry creating failed daemon pods"

test/e2e/apimachinery/webhook.go

Lines changed: 171 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const (
7676
addedLabelValue = "yes"
7777
)
7878

79-
var _ = SIGDescribe("AdmissionWebhook", func() {
79+
var _ = SIGDescribe("AdmissionWebhook [Privileged:ClusterAdmin]", func() {
8080
var context *certContext
8181
f := framework.NewDefaultFramework("webhook")
8282
servicePort := int32(8443)
@@ -104,7 +104,15 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
104104
cleanWebhookTest(client, namespaceName)
105105
})
106106

107-
ginkgo.It("should include webhook resources in discovery documents", func() {
107+
/*
108+
Release : v1.16
109+
Testname: Admission webhook, discovery document
110+
Description: The admissionregistration.k8s.io API group MUST exists in the /apis discovery document.
111+
The admissionregistration.k8s.io/v1 API group/version MUST exists in the /apis discovery document.
112+
The mutatingwebhookconfigurations and validatingwebhookconfigurations resources MUST exist in the
113+
/apis/admissionregistration.k8s.io/v1 discovery document.
114+
*/
115+
framework.ConformanceIt("should include webhook resources in discovery documents", func() {
108116
{
109117
ginkgo.By("fetching the /apis discovery document")
110118
apiGroupList := &metav1.APIGroupList{}
@@ -175,19 +183,40 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
175183
}
176184
})
177185

178-
ginkgo.It("Should be able to deny pod and configmap creation", func() {
186+
/*
187+
Release : v1.16
188+
Testname: Admission webhook, deny create
189+
Description: Register an admission webhook configuration that admits pod and configmap. Attempts to create
190+
non-compliant pods and configmaps, or update/patch compliant pods and configmaps to be non-compliant MUST
191+
be denied. An attempt to create a pod that causes a webhook to hang MUST result in a webhook timeout error,
192+
and the pod creation MUST be denied. An attempt to create a non-compliant configmap in a whitelisted
193+
namespace based on the webhook namespace selector MUST be allowed.
194+
*/
195+
framework.ConformanceIt("should be able to deny pod and configmap creation", func() {
179196
webhookCleanup := registerWebhook(f, f.UniqueName, context, servicePort)
180197
defer webhookCleanup()
181198
testWebhook(f)
182199
})
183200

184-
ginkgo.It("Should be able to deny attaching pod", func() {
201+
/*
202+
Release : v1.16
203+
Testname: Admission webhook, deny attach
204+
Description: Register an admission webhook configuration that denies connecting to a pod's attach sub-resource.
205+
Attempts to attach MUST be denied.
206+
*/
207+
framework.ConformanceIt("should be able to deny attaching pod", func() {
185208
webhookCleanup := registerWebhookForAttachingPod(f, f.UniqueName, context, servicePort)
186209
defer webhookCleanup()
187210
testAttachingPodWebhook(f)
188211
})
189212

190-
ginkgo.It("Should be able to deny custom resource creation and deletion", func() {
213+
/*
214+
Release : v1.16
215+
Testname: Admission webhook, deny custom resource create and delete
216+
Description: Register an admission webhook configuration that denies creation, update and deletion of
217+
custom resources. Attempts to create, update and delete custom resources MUST be denied.
218+
*/
219+
framework.ConformanceIt("should be able to deny custom resource creation, update and deletion", func() {
191220
testcrd, err := crd.CreateTestCRD(f)
192221
if err != nil {
193222
return
@@ -196,36 +225,68 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
196225
webhookCleanup := registerWebhookForCustomResource(f, f.UniqueName, context, testcrd, servicePort)
197226
defer webhookCleanup()
198227
testCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClients["v1"])
199-
testBlockingCustomResourceDeletion(f, testcrd.Crd, testcrd.DynamicClients["v1"])
228+
testBlockingCustomResourceUpdateDeletion(f, testcrd.Crd, testcrd.DynamicClients["v1"])
200229
})
201230

202-
ginkgo.It("Should unconditionally reject operations on fail closed webhook", func() {
231+
/*
232+
Release : v1.16
233+
Testname: Admission webhook, fail closed
234+
Description: Register a webhook with a fail closed policy and without CA bundle so that it cannot be called.
235+
Attempt operations that require the admission webhook; all MUST be denied.
236+
*/
237+
framework.ConformanceIt("should unconditionally reject operations on fail closed webhook", func() {
203238
webhookCleanup := registerFailClosedWebhook(f, f.UniqueName, context, servicePort)
204239
defer webhookCleanup()
205240
testFailClosedWebhook(f)
206241
})
207242

208-
ginkgo.It("Should mutate configmap", func() {
243+
/*
244+
Release : v1.16
245+
Testname: Admission webhook, ordered mutation
246+
Description: Register a mutating webhook configuration with two webhooks that admit configmaps, one that
247+
adds a data key if the configmap already has a specific key, and another that adds a key if the key added by
248+
the first webhook is present. Attempt to create a config map; both keys MUST be added to the config map.
249+
*/
250+
framework.ConformanceIt("should mutate configmap", func() {
209251
webhookCleanup := registerMutatingWebhookForConfigMap(f, f.UniqueName, context, servicePort)
210252
defer webhookCleanup()
211253
testMutatingConfigMapWebhook(f)
212254
})
213255

214-
ginkgo.It("Should mutate pod and apply defaults after mutation", func() {
256+
/*
257+
Release : v1.16
258+
Testname: Admission webhook, mutation with defaulting
259+
Description: Register a mutating webhook that adds an InitContainer to pods. Attempt to create a pod;
260+
the InitContainer MUST be added the TerminationMessagePolicy MUST be defaulted.
261+
*/
262+
framework.ConformanceIt("should mutate pod and apply defaults after mutation", func() {
215263
webhookCleanup := registerMutatingWebhookForPod(f, f.UniqueName, context, servicePort)
216264
defer webhookCleanup()
217265
testMutatingPodWebhook(f)
218266
})
219267

220-
ginkgo.It("Should not be able to mutate or prevent deletion of webhook configuration objects", func() {
268+
/*
269+
Release : v1.16
270+
Testname: Admission webhook, admission control not allowed on webhook configuration objects
271+
Description: Register webhooks that mutate and deny deletion of webhook configuration objects. Attempt to create
272+
and delete a webhook configuration object; both operations MUST be allowed and the webhook configuration object
273+
MUST NOT be mutated the the webhooks.
274+
*/
275+
framework.ConformanceIt("should not be able to mutate or prevent deletion of webhook configuration objects", func() {
221276
validatingWebhookCleanup := registerValidatingWebhookForWebhookConfigurations(f, f.UniqueName+"blocking", context, servicePort)
222277
defer validatingWebhookCleanup()
223278
mutatingWebhookCleanup := registerMutatingWebhookForWebhookConfigurations(f, f.UniqueName+"blocking", context, servicePort)
224279
defer mutatingWebhookCleanup()
225280
testWebhooksForWebhookConfigurations(f, f.UniqueName, context, servicePort)
226281
})
227282

228-
ginkgo.It("Should mutate custom resource", func() {
283+
/*
284+
Release : v1.16
285+
Testname: Admission webhook, mutate custom resource
286+
Description: Register a webhook that mutates a custom resource. Attempt to create custom resource object;
287+
the custom resource MUST be mutated.
288+
*/
289+
framework.ConformanceIt("should mutate custom resource", func() {
229290
testcrd, err := crd.CreateTestCRD(f)
230291
if err != nil {
231292
return
@@ -236,14 +297,28 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
236297
testMutatingCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClients["v1"], false)
237298
})
238299

239-
ginkgo.It("Should deny crd creation", func() {
300+
/*
301+
Release : v1.16
302+
Testname: Admission webhook, deny custom resource definition
303+
Description: Register a webhook that denies custom resource definition create. Attempt to create a
304+
custom resource definition; the create request MUST be denied.
305+
*/
306+
framework.ConformanceIt("should deny crd creation", func() {
240307
crdWebhookCleanup := registerValidatingWebhookForCRD(f, f.UniqueName, context, servicePort)
241308
defer crdWebhookCleanup()
242309

243310
testCRDDenyWebhook(f)
244311
})
245312

246-
ginkgo.It("Should mutate custom resource with different stored version", func() {
313+
/*
314+
Release : v1.16
315+
Testname: Admission webhook, mutate custom resource with different stored version
316+
Description: Register a webhook that mutates custom resources on create and update. Register a custom resource
317+
definition using v1 as stored version. Create a custom resource. Patch the custom resource definition to use v2 as
318+
the stored version. Attempt to patch the custom resource with a new field and value; the patch MUST be applied
319+
successfully.
320+
*/
321+
framework.ConformanceIt("should mutate custom resource with different stored version", func() {
247322
testcrd, err := createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f)
248323
if err != nil {
249324
return
@@ -254,7 +329,14 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
254329
testMultiVersionCustomResourceWebhook(f, testcrd)
255330
})
256331

257-
ginkgo.It("Should mutate custom resource with pruning", func() {
332+
/*
333+
Release : v1.16
334+
Testname: Admission webhook, mutate custom resource with pruning
335+
Description: Register mutating webhooks that adds fields to custom objects. Register a custom resource definition
336+
with a schema that includes only one of the data keys added by the webhooks. Attempt to a custom resource;
337+
the fields included in the schema MUST be present and field not included in the schema MUST NOT be present.
338+
*/
339+
framework.ConformanceIt("should mutate custom resource with pruning", func() {
258340
const prune = true
259341
testcrd, err := createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f, func(crd *apiextensionsv1.CustomResourceDefinition) {
260342
crd.Spec.PreserveUnknownFields = false
@@ -285,7 +367,16 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
285367
testMutatingCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClients["v1"], prune)
286368
})
287369

288-
ginkgo.It("Should honor timeout", func() {
370+
/*
371+
Release : v1.16
372+
Testname: Admission webhook, honor timeout
373+
Description: Using a webhook that waits 5 seconds before admitting objects, configure the webhook with combinations
374+
of timeouts and failure policy values. Attempt to create a config map with each combination. Requests MUST
375+
timeout if the configured webhook timeout is less than 5 seconds and failure policy is fail. Requests must not timeout if
376+
the failure policy is ignore. Requests MUST NOT timeout if configured webhook timeout is 10 seconds (much longer
377+
than the webhook wait duration).
378+
*/
379+
framework.ConformanceIt("should honor timeout", func() {
289380
policyFail := admissionregistrationv1.Fail
290381
policyIgnore := admissionregistrationv1.Ignore
291382

@@ -310,7 +401,14 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
310401
slowWebhookCleanup()
311402
})
312403

313-
ginkgo.It("patching/updating a validating webhook should work", func() {
404+
/*
405+
Release : v1.16
406+
Testname: Admission webhook, update validating webhook
407+
Description: Register a validating admission webhook configuration. Update the webhook to not apply to the create
408+
operation and attempt to create an object; the webhook MUST NOT deny the create. Patch the webhook to apply to the
409+
create operation again and attempt to create an object; the webhook MUST deny the create.
410+
*/
411+
framework.ConformanceIt("patching/updating a validating webhook should work", func() {
314412
client := f.ClientSet
315413
admissionClient := client.AdmissionregistrationV1()
316414

@@ -393,7 +491,14 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
393491
framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be denied creation by validating webhook", f.Namespace.Name)
394492
})
395493

396-
ginkgo.It("patching/updating a mutating webhook should work", func() {
494+
/*
495+
Release : v1.16
496+
Testname: Admission webhook, update mutating webhook
497+
Description: Register a mutating admission webhook configuration. Update the webhook to not apply to the create
498+
operation and attempt to create an object; the webhook MUST NOT mutate the object. Patch the webhook to apply to the
499+
create operation again and attempt to create an object; the webhook MUST mutate the object.
500+
*/
501+
framework.ConformanceIt("patching/updating a mutating webhook should work", func() {
397502
client := f.ClientSet
398503
admissionClient := client.AdmissionregistrationV1()
399504

@@ -455,7 +560,15 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
455560
framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be mutated", f.Namespace.Name)
456561
})
457562

458-
ginkgo.It("listing validating webhooks should work", func() {
563+
/*
564+
Release : v1.16
565+
Testname: Admission webhook, list validating webhooks
566+
Description: Create 10 validating webhook configurations, all with a label. Attempt to list the webhook
567+
configurations matching the label; all the created webhook configurations MUST be present. Attempt to create an
568+
object; the create MUST be denied. Attempt to remove the webhook configurations matching the label with deletecollection;
569+
all webhook configurations MUST be deleted. Attempt to create an object; the create MUST NOT be denied.
570+
*/
571+
framework.ConformanceIt("listing validating webhooks should work", func() {
459572
testListSize := 10
460573
testUUID := string(uuid.NewUUID())
461574

@@ -516,7 +629,15 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
516629
framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be allowed creation since there are no webhooks", f.Namespace.Name)
517630
})
518631

519-
ginkgo.It("listing mutating webhooks should work", func() {
632+
/*
633+
Release : v1.16
634+
Testname: Admission webhook, list mutating webhooks
635+
Description: Create 10 mutating webhook configurations, all with a label. Attempt to list the webhook
636+
configurations matching the label; all the created webhook configurations MUST be present. Attempt to create an
637+
object; the object MUST be mutated. Attempt to remove the webhook configurations matching the label with deletecollection;
638+
all webhook configurations MUST be deleted. Attempt to create an object; the object MUST NOT be mutated.
639+
*/
640+
framework.ConformanceIt("listing mutating webhooks should work", func() {
520641
testListSize := 10
521642
testUUID := string(uuid.NewUUID())
522643

@@ -907,8 +1028,8 @@ func registerMutatingWebhookForPod(f *framework.Framework, configName string, co
9071028
func testMutatingPodWebhook(f *framework.Framework) {
9081029
ginkgo.By("create a pod that should be updated by the webhook")
9091030
client := f.ClientSet
910-
configMap := toBeMutatedPod(f)
911-
mutatedPod, err := client.CoreV1().Pods(f.Namespace.Name).Create(configMap)
1031+
pod := toBeMutatedPod(f)
1032+
mutatedPod, err := client.CoreV1().Pods(f.Namespace.Name).Create(pod)
9121033
gomega.Expect(err).To(gomega.BeNil())
9131034
if len(mutatedPod.Spec.InitContainers) != 1 {
9141035
e2elog.Failf("expect pod to have 1 init container, got %#v", mutatedPod.Spec.InitContainers)
@@ -1738,7 +1859,7 @@ func testCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1.Cust
17381859
}
17391860
}
17401861

1741-
func testBlockingCustomResourceDeletion(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
1862+
func testBlockingCustomResourceUpdateDeletion(f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
17421863
ginkgo.By("Creating a custom resource whose deletion would be denied by the webhook")
17431864
crInstanceName := "cr-instance-2"
17441865
crInstance := &unstructured.Unstructured{
@@ -1757,6 +1878,22 @@ func testBlockingCustomResourceDeletion(f *framework.Framework, crd *apiextensio
17571878
_, err := customResourceClient.Create(crInstance, metav1.CreateOptions{})
17581879
framework.ExpectNoError(err, "failed to create custom resource %s in namespace: %s", crInstanceName, f.Namespace.Name)
17591880

1881+
ginkgo.By("Updating the custom resource with disallowed data should be denied")
1882+
toNonCompliantFn := func(cr *unstructured.Unstructured) {
1883+
if _, ok := cr.Object["data"]; !ok {
1884+
cr.Object["data"] = map[string]interface{}{}
1885+
}
1886+
data := cr.Object["data"].(map[string]interface{})
1887+
data["webhook-e2e-test"] = "webhook-disallow"
1888+
}
1889+
_, err = updateCustomResource(customResourceClient, f.Namespace.Name, crInstanceName, toNonCompliantFn)
1890+
framework.ExpectError(err, "updating custom resource %s in namespace: %s should be denied", crInstanceName, f.Namespace.Name)
1891+
1892+
expectedErrMsg := "the custom resource contains unwanted data"
1893+
if !strings.Contains(err.Error(), expectedErrMsg) {
1894+
e2elog.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
1895+
}
1896+
17601897
ginkgo.By("Deleting the custom resource should be denied")
17611898
err = customResourceClient.Delete(crInstanceName, &metav1.DeleteOptions{})
17621899
framework.ExpectError(err, "deleting custom resource %s in namespace: %s should be denied", crInstanceName, f.Namespace.Name)
@@ -1860,8 +1997,19 @@ func testMultiVersionCustomResourceWebhook(f *framework.Framework, testcrd *crd.
18601997

18611998
ginkgo.By("Patching the custom resource while v2 is storage version")
18621999
crDummyPatch := fmt.Sprint(`[{ "op": "add", "path": "/dummy", "value": "test" }]`)
1863-
_, err = testcrd.DynamicClients["v2"].Patch(crName, types.JSONPatchType, []byte(crDummyPatch), metav1.PatchOptions{})
2000+
mutatedCR, err := testcrd.DynamicClients["v2"].Patch(crName, types.JSONPatchType, []byte(crDummyPatch), metav1.PatchOptions{})
18642001
framework.ExpectNoError(err, "failed to patch custom resource %s in namespace: %s", crName, f.Namespace.Name)
2002+
expectedCRData := map[string]interface{}{
2003+
"mutation-start": "yes",
2004+
"mutation-stage-1": "yes",
2005+
"mutation-stage-2": "yes",
2006+
}
2007+
if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
2008+
e2elog.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
2009+
}
2010+
if !reflect.DeepEqual("test", mutatedCR.Object["dummy"]) {
2011+
e2elog.Failf("\nexpected %#v\n, got %#v\n", "test", mutatedCR.Object["dummy"])
2012+
}
18652013
}
18662014

18672015
func registerValidatingWebhookForCRD(f *framework.Framework, configName string, context *certContext, servicePort int32) func() {

0 commit comments

Comments
 (0)