mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	Added e2e test to check admission webhook works for pods/attach.
This commit is contained in:
		@@ -54,6 +54,7 @@ const (
 | 
				
			|||||||
	// The webhook configuration names should not be reused between test instances.
 | 
						// The webhook configuration names should not be reused between test instances.
 | 
				
			||||||
	crWebhookConfigName           = "e2e-test-webhook-config-cr"
 | 
						crWebhookConfigName           = "e2e-test-webhook-config-cr"
 | 
				
			||||||
	webhookConfigName             = "e2e-test-webhook-config"
 | 
						webhookConfigName             = "e2e-test-webhook-config"
 | 
				
			||||||
 | 
						attachingPodWebhookConfigName = "e2e-test-webhook-config-attaching-pod"
 | 
				
			||||||
	mutatingWebhookConfigName     = "e2e-test-mutating-webhook-config"
 | 
						mutatingWebhookConfigName     = "e2e-test-mutating-webhook-config"
 | 
				
			||||||
	podMutatingWebhookConfigName  = "e2e-test-mutating-webhook-pod"
 | 
						podMutatingWebhookConfigName  = "e2e-test-mutating-webhook-pod"
 | 
				
			||||||
	crMutatingWebhookConfigName   = "e2e-test-mutating-webhook-config-cr"
 | 
						crMutatingWebhookConfigName   = "e2e-test-mutating-webhook-config-cr"
 | 
				
			||||||
@@ -67,6 +68,7 @@ const (
 | 
				
			|||||||
	skipNamespaceLabelValue = "yes"
 | 
						skipNamespaceLabelValue = "yes"
 | 
				
			||||||
	skippedNamespaceName    = "exempted-namesapce"
 | 
						skippedNamespaceName    = "exempted-namesapce"
 | 
				
			||||||
	disallowedPodName       = "disallowed-pod"
 | 
						disallowedPodName       = "disallowed-pod"
 | 
				
			||||||
 | 
						toBeAttachedPodName     = "to-be-attached-pod"
 | 
				
			||||||
	hangingPodName          = "hanging-pod"
 | 
						hangingPodName          = "hanging-pod"
 | 
				
			||||||
	disallowedConfigMapName = "disallowed-configmap"
 | 
						disallowedConfigMapName = "disallowed-configmap"
 | 
				
			||||||
	allowedConfigMapName    = "allowed-configmap"
 | 
						allowedConfigMapName    = "allowed-configmap"
 | 
				
			||||||
@@ -117,6 +119,12 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
 | 
				
			|||||||
		testWebhook(f)
 | 
							testWebhook(f)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It("Should be able to deny attaching pod", func() {
 | 
				
			||||||
 | 
							webhookCleanup := registerWebhookForAttachingPod(f, context)
 | 
				
			||||||
 | 
							defer webhookCleanup()
 | 
				
			||||||
 | 
							testAttachingPodWebhook(f)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	It("Should be able to deny custom resource creation", func() {
 | 
						It("Should be able to deny custom resource creation", func() {
 | 
				
			||||||
		testcrd, err := framework.CreateTestCRD(f)
 | 
							testcrd, err := framework.CreateTestCRD(f)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@@ -405,6 +413,53 @@ func registerWebhook(f *framework.Framework, context *certContext) func() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func registerWebhookForAttachingPod(f *framework.Framework, context *certContext) func() {
 | 
				
			||||||
 | 
						client := f.ClientSet
 | 
				
			||||||
 | 
						By("Registering the webhook via the AdmissionRegistration API")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						namespace := f.Namespace.Name
 | 
				
			||||||
 | 
						configName := attachingPodWebhookConfigName
 | 
				
			||||||
 | 
						// A webhook that cannot talk to server, with fail-open policy
 | 
				
			||||||
 | 
						failOpenHook := failingWebhook(namespace, "fail-open.k8s.io")
 | 
				
			||||||
 | 
						policyIgnore := v1beta1.Ignore
 | 
				
			||||||
 | 
						failOpenHook.FailurePolicy = &policyIgnore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&v1beta1.ValidatingWebhookConfiguration{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: configName,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Webhooks: []v1beta1.Webhook{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name: "deny-attaching-pod.k8s.io",
 | 
				
			||||||
 | 
									Rules: []v1beta1.RuleWithOperations{{
 | 
				
			||||||
 | 
										Operations: []v1beta1.OperationType{v1beta1.Connect},
 | 
				
			||||||
 | 
										Rule: v1beta1.Rule{
 | 
				
			||||||
 | 
											APIGroups:   []string{""},
 | 
				
			||||||
 | 
											APIVersions: []string{"v1"},
 | 
				
			||||||
 | 
											Resources:   []string{"pods/attach"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									}},
 | 
				
			||||||
 | 
									ClientConfig: v1beta1.WebhookClientConfig{
 | 
				
			||||||
 | 
										Service: &v1beta1.ServiceReference{
 | 
				
			||||||
 | 
											Namespace: namespace,
 | 
				
			||||||
 | 
											Name:      serviceName,
 | 
				
			||||||
 | 
											Path:      strPtr("/pods/attach"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										CABundle: context.signingCert,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The webhook configuration is honored in 10s.
 | 
				
			||||||
 | 
						time.Sleep(10 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return func() {
 | 
				
			||||||
 | 
							client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Delete(configName, nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func registerMutatingWebhookForConfigMap(f *framework.Framework, context *certContext) func() {
 | 
					func registerMutatingWebhookForConfigMap(f *framework.Framework, context *certContext) func() {
 | 
				
			||||||
	client := f.ClientSet
 | 
						client := f.ClientSet
 | 
				
			||||||
	By("Registering the mutating configmap webhook via the AdmissionRegistration API")
 | 
						By("Registering the mutating configmap webhook via the AdmissionRegistration API")
 | 
				
			||||||
@@ -642,6 +697,21 @@ func testWebhook(f *framework.Framework) {
 | 
				
			|||||||
	Expect(err).To(BeNil())
 | 
						Expect(err).To(BeNil())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func testAttachingPodWebhook(f *framework.Framework) {
 | 
				
			||||||
 | 
						By("create a pod")
 | 
				
			||||||
 | 
						client := f.ClientSet
 | 
				
			||||||
 | 
						pod := toBeAttachedPod(f)
 | 
				
			||||||
 | 
						_, err := client.CoreV1().Pods(f.Namespace.Name).Create(pod)
 | 
				
			||||||
 | 
						Expect(err).To(BeNil())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						By("'kubectl attach' the pod, should be denied by the webhook")
 | 
				
			||||||
 | 
						_, err = framework.NewKubectlCommand("attach", fmt.Sprintf("--namespace=%v", f.Namespace.Name), pod.Name, "-i", "-c=container1").Exec()
 | 
				
			||||||
 | 
						Expect(err).NotTo(BeNil())
 | 
				
			||||||
 | 
						if e, a := "attaching to pod 'to-be-attached-pod' is not allowed", err.Error(); !strings.Contains(a, e) {
 | 
				
			||||||
 | 
							framework.Failf("unexpected 'kubectl attach' error message. expected to contain %q, got %q", e, a)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// failingWebhook returns a webhook with rule of create configmaps,
 | 
					// failingWebhook returns a webhook with rule of create configmaps,
 | 
				
			||||||
// but with an invalid client config so that server cannot communicate with it
 | 
					// but with an invalid client config so that server cannot communicate with it
 | 
				
			||||||
func failingWebhook(namespace, name string) v1beta1.Webhook {
 | 
					func failingWebhook(namespace, name string) v1beta1.Webhook {
 | 
				
			||||||
@@ -930,6 +1000,22 @@ func hangingPod(f *framework.Framework) *v1.Pod {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func toBeAttachedPod(f *framework.Framework) *v1.Pod {
 | 
				
			||||||
 | 
						return &v1.Pod{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: toBeAttachedPodName,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: v1.PodSpec{
 | 
				
			||||||
 | 
								Containers: []v1.Container{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Name:  "container1",
 | 
				
			||||||
 | 
										Image: imageutils.GetPauseImageName(),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func nonCompliantConfigMap(f *framework.Framework) *v1.ConfigMap {
 | 
					func nonCompliantConfigMap(f *framework.Framework) *v1.ConfigMap {
 | 
				
			||||||
	return &v1.ConfigMap{
 | 
						return &v1.ConfigMap{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
1.12v1
 | 
					1.12v2
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,7 +60,7 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	glog.V(2).Info(fmt.Sprintf("handling request: %v", body))
 | 
						glog.V(2).Info(fmt.Sprintf("handling request: %s", body))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The AdmissionReview that was sent to the webhook
 | 
						// The AdmissionReview that was sent to the webhook
 | 
				
			||||||
	requestedAdmissionReview := v1beta1.AdmissionReview{}
 | 
						requestedAdmissionReview := v1beta1.AdmissionReview{}
 | 
				
			||||||
@@ -99,6 +99,10 @@ func servePods(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	serve(w, r, admitPods)
 | 
						serve(w, r, admitPods)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func serveAttachingPods(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						serve(w, r, denySpecificAttachment)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func serveMutatePods(w http.ResponseWriter, r *http.Request) {
 | 
					func serveMutatePods(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	serve(w, r, mutatePods)
 | 
						serve(w, r, mutatePods)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -130,6 +134,7 @@ func main() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	http.HandleFunc("/always-deny", serveAlwaysDeny)
 | 
						http.HandleFunc("/always-deny", serveAlwaysDeny)
 | 
				
			||||||
	http.HandleFunc("/pods", servePods)
 | 
						http.HandleFunc("/pods", servePods)
 | 
				
			||||||
 | 
						http.HandleFunc("/pods/attach", serveAttachingPods)
 | 
				
			||||||
	http.HandleFunc("/mutating-pods", serveMutatePods)
 | 
						http.HandleFunc("/mutating-pods", serveMutatePods)
 | 
				
			||||||
	http.HandleFunc("/configmaps", serveConfigmaps)
 | 
						http.HandleFunc("/configmaps", serveConfigmaps)
 | 
				
			||||||
	http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
 | 
						http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,3 +101,41 @@ func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return &reviewResponse
 | 
						return &reviewResponse
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// denySpecificAttachment denies `kubectl attach to-be-attached-pod -i -c=container1"
 | 
				
			||||||
 | 
					// or equivalent client requests.
 | 
				
			||||||
 | 
					func denySpecificAttachment(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
 | 
				
			||||||
 | 
						glog.V(2).Info("handling attaching pods")
 | 
				
			||||||
 | 
						if ar.Request.Name != "to-be-attached-pod" {
 | 
				
			||||||
 | 
							return &v1beta1.AdmissionResponse{Allowed: true}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
 | 
				
			||||||
 | 
						if e, a := podResource, ar.Request.Resource; e != a {
 | 
				
			||||||
 | 
							err := fmt.Errorf("expect resource to be %s, got %s", e, a)
 | 
				
			||||||
 | 
							glog.Error(err)
 | 
				
			||||||
 | 
							return toAdmissionResponse(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if e, a := "attach", ar.Request.SubResource; e != a {
 | 
				
			||||||
 | 
							err := fmt.Errorf("expect subresource to be %s, got %s", e, a)
 | 
				
			||||||
 | 
							glog.Error(err)
 | 
				
			||||||
 | 
							return toAdmissionResponse(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						raw := ar.Request.Object.Raw
 | 
				
			||||||
 | 
						podAttachOptions := corev1.PodAttachOptions{}
 | 
				
			||||||
 | 
						deserializer := codecs.UniversalDeserializer()
 | 
				
			||||||
 | 
						if _, _, err := deserializer.Decode(raw, nil, &podAttachOptions); err != nil {
 | 
				
			||||||
 | 
							glog.Error(err)
 | 
				
			||||||
 | 
							return toAdmissionResponse(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						glog.V(2).Info(fmt.Sprintf("podAttachOptions=%#v\n", podAttachOptions))
 | 
				
			||||||
 | 
						if !podAttachOptions.Stdin || podAttachOptions.Container != "container1" {
 | 
				
			||||||
 | 
							return &v1beta1.AdmissionResponse{Allowed: true}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &v1beta1.AdmissionResponse{
 | 
				
			||||||
 | 
							Allowed: false,
 | 
				
			||||||
 | 
							Result: &metav1.Status{
 | 
				
			||||||
 | 
								Message: "attaching to pod 'to-be-attached-pod' is not allowed",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ func (i *ImageConfig) SetVersion(version string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	AdmissionWebhook         = ImageConfig{e2eRegistry, "webhook", "1.12v1", false}
 | 
						AdmissionWebhook         = ImageConfig{e2eRegistry, "webhook", "1.12v2", false}
 | 
				
			||||||
	APIServer                = ImageConfig{e2eRegistry, "sample-apiserver", "1.0", false}
 | 
						APIServer                = ImageConfig{e2eRegistry, "sample-apiserver", "1.0", false}
 | 
				
			||||||
	AppArmorLoader           = ImageConfig{gcRegistry, "apparmor-loader", "0.1", false}
 | 
						AppArmorLoader           = ImageConfig{gcRegistry, "apparmor-loader", "0.1", false}
 | 
				
			||||||
	BusyBox                  = ImageConfig{gcRegistry, "busybox", "1.24", false}
 | 
						BusyBox                  = ImageConfig{gcRegistry, "busybox", "1.24", false}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user