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.
 | 
			
		||||
	crWebhookConfigName           = "e2e-test-webhook-config-cr"
 | 
			
		||||
	webhookConfigName             = "e2e-test-webhook-config"
 | 
			
		||||
	attachingPodWebhookConfigName = "e2e-test-webhook-config-attaching-pod"
 | 
			
		||||
	mutatingWebhookConfigName     = "e2e-test-mutating-webhook-config"
 | 
			
		||||
	podMutatingWebhookConfigName  = "e2e-test-mutating-webhook-pod"
 | 
			
		||||
	crMutatingWebhookConfigName   = "e2e-test-mutating-webhook-config-cr"
 | 
			
		||||
@@ -67,6 +68,7 @@ const (
 | 
			
		||||
	skipNamespaceLabelValue = "yes"
 | 
			
		||||
	skippedNamespaceName    = "exempted-namesapce"
 | 
			
		||||
	disallowedPodName       = "disallowed-pod"
 | 
			
		||||
	toBeAttachedPodName     = "to-be-attached-pod"
 | 
			
		||||
	hangingPodName          = "hanging-pod"
 | 
			
		||||
	disallowedConfigMapName = "disallowed-configmap"
 | 
			
		||||
	allowedConfigMapName    = "allowed-configmap"
 | 
			
		||||
@@ -117,6 +119,12 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
 | 
			
		||||
		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() {
 | 
			
		||||
		testcrd, err := framework.CreateTestCRD(f)
 | 
			
		||||
		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() {
 | 
			
		||||
	client := f.ClientSet
 | 
			
		||||
	By("Registering the mutating configmap webhook via the AdmissionRegistration API")
 | 
			
		||||
@@ -642,6 +697,21 @@ func testWebhook(f *framework.Framework) {
 | 
			
		||||
	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,
 | 
			
		||||
// but with an invalid client config so that server cannot communicate with it
 | 
			
		||||
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 {
 | 
			
		||||
	return &v1.ConfigMap{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
1.12v1
 | 
			
		||||
1.12v2
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
 | 
			
		||||
		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
 | 
			
		||||
	requestedAdmissionReview := v1beta1.AdmissionReview{}
 | 
			
		||||
@@ -99,6 +99,10 @@ func servePods(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	serve(w, r, admitPods)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serveAttachingPods(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	serve(w, r, denySpecificAttachment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serveMutatePods(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	serve(w, r, mutatePods)
 | 
			
		||||
}
 | 
			
		||||
@@ -130,6 +134,7 @@ func main() {
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/always-deny", serveAlwaysDeny)
 | 
			
		||||
	http.HandleFunc("/pods", servePods)
 | 
			
		||||
	http.HandleFunc("/pods/attach", serveAttachingPods)
 | 
			
		||||
	http.HandleFunc("/mutating-pods", serveMutatePods)
 | 
			
		||||
	http.HandleFunc("/configmaps", serveConfigmaps)
 | 
			
		||||
	http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
 | 
			
		||||
 
 | 
			
		||||
@@ -101,3 +101,41 @@ func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
 | 
			
		||||
	}
 | 
			
		||||
	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 (
 | 
			
		||||
	AdmissionWebhook         = ImageConfig{e2eRegistry, "webhook", "1.12v1", false}
 | 
			
		||||
	AdmissionWebhook         = ImageConfig{e2eRegistry, "webhook", "1.12v2", false}
 | 
			
		||||
	APIServer                = ImageConfig{e2eRegistry, "sample-apiserver", "1.0", false}
 | 
			
		||||
	AppArmorLoader           = ImageConfig{gcRegistry, "apparmor-loader", "0.1", false}
 | 
			
		||||
	BusyBox                  = ImageConfig{gcRegistry, "busybox", "1.24", false}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user