1
16
17 package webhook
18
19 import (
20 "fmt"
21 "strings"
22
23 "k8s.io/api/admission/v1"
24 corev1 "k8s.io/api/core/v1"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/klog/v2"
27 )
28
29 const (
30 podsInitContainerPatch string = `[
31 {"op":"add","path":"/spec/initContainers","value":[{"image":"webhook-added-image","name":"webhook-added-init-container","resources":{}}]}
32 ]`
33 podsSidecarPatch string = `[
34 {"op":"add", "path":"/spec/containers/-","value":{"image":"%v","name":"webhook-added-sidecar","resources":{}}}
35 ]`
36 )
37
38
39 func admitPods(ar v1.AdmissionReview) *v1.AdmissionResponse {
40 klog.V(2).Info("admitting pods")
41 podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
42 if ar.Request.Resource != podResource {
43 err := fmt.Errorf("expect resource to be %s", podResource)
44 klog.Error(err)
45 return toV1AdmissionResponse(err)
46 }
47
48 raw := ar.Request.Object.Raw
49 pod := corev1.Pod{}
50 deserializer := codecs.UniversalDeserializer()
51 if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
52 klog.Error(err)
53 return toV1AdmissionResponse(err)
54 }
55 reviewResponse := v1.AdmissionResponse{}
56 reviewResponse.Allowed = true
57
58 var msg string
59 if v, ok := pod.Labels["webhook-e2e-test"]; ok {
60 if v == "webhook-disallow" {
61 reviewResponse.Allowed = false
62 msg = msg + "the pod contains unwanted label; "
63 }
64 if v == "wait-forever" {
65 reviewResponse.Allowed = false
66 msg = msg + "the pod response should not be sent; "
67 <-make(chan int)
68 }
69 }
70 for _, container := range pod.Spec.Containers {
71 if strings.Contains(container.Name, "webhook-disallow") {
72 reviewResponse.Allowed = false
73 msg = msg + "the pod contains unwanted container name; "
74 }
75 }
76 if !reviewResponse.Allowed {
77 reviewResponse.Result = &metav1.Status{Message: strings.TrimSpace(msg)}
78 }
79 return &reviewResponse
80 }
81
82 func mutatePods(ar v1.AdmissionReview) *v1.AdmissionResponse {
83 shouldPatchPod := func(pod *corev1.Pod) bool {
84 if pod.Name != "webhook-to-be-mutated" {
85 return false
86 }
87 return !hasContainer(pod.Spec.InitContainers, "webhook-added-init-container")
88 }
89 return applyPodPatch(ar, shouldPatchPod, podsInitContainerPatch)
90 }
91
92 func mutatePodsSidecar(ar v1.AdmissionReview) *v1.AdmissionResponse {
93 if sidecarImage == "" {
94 return &v1.AdmissionResponse{
95 Allowed: false,
96 Result: &metav1.Status{
97 Status: "Failure",
98 Message: "No image specified by the sidecar-image parameter",
99 Code: 500,
100 },
101 }
102 }
103 shouldPatchPod := func(pod *corev1.Pod) bool {
104 return !hasContainer(pod.Spec.Containers, "webhook-added-sidecar")
105 }
106 return applyPodPatch(ar, shouldPatchPod, fmt.Sprintf(podsSidecarPatch, sidecarImage))
107 }
108
109 func hasContainer(containers []corev1.Container, containerName string) bool {
110 for _, container := range containers {
111 if container.Name == containerName {
112 return true
113 }
114 }
115 return false
116 }
117
118 func applyPodPatch(ar v1.AdmissionReview, shouldPatchPod func(*corev1.Pod) bool, patch string) *v1.AdmissionResponse {
119 klog.V(2).Info("mutating pods")
120 podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
121 if ar.Request.Resource != podResource {
122 klog.Errorf("expect resource to be %s", podResource)
123 return nil
124 }
125
126 raw := ar.Request.Object.Raw
127 pod := corev1.Pod{}
128 deserializer := codecs.UniversalDeserializer()
129 if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
130 klog.Error(err)
131 return toV1AdmissionResponse(err)
132 }
133 reviewResponse := v1.AdmissionResponse{}
134 reviewResponse.Allowed = true
135 if shouldPatchPod(&pod) {
136 reviewResponse.Patch = []byte(patch)
137 pt := v1.PatchTypeJSONPatch
138 reviewResponse.PatchType = &pt
139 }
140 return &reviewResponse
141 }
142
143
144
145 func denySpecificAttachment(ar v1.AdmissionReview) *v1.AdmissionResponse {
146 klog.V(2).Info("handling attaching pods")
147 if ar.Request.Name != "to-be-attached-pod" {
148 return &v1.AdmissionResponse{Allowed: true}
149 }
150 podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
151 if e, a := podResource, ar.Request.Resource; e != a {
152 err := fmt.Errorf("expect resource to be %s, got %s", e, a)
153 klog.Error(err)
154 return toV1AdmissionResponse(err)
155 }
156 if e, a := "attach", ar.Request.SubResource; e != a {
157 err := fmt.Errorf("expect subresource to be %s, got %s", e, a)
158 klog.Error(err)
159 return toV1AdmissionResponse(err)
160 }
161
162 raw := ar.Request.Object.Raw
163 podAttachOptions := corev1.PodAttachOptions{}
164 deserializer := codecs.UniversalDeserializer()
165 if _, _, err := deserializer.Decode(raw, nil, &podAttachOptions); err != nil {
166 klog.Error(err)
167 return toV1AdmissionResponse(err)
168 }
169 klog.V(2).Info(fmt.Sprintf("podAttachOptions=%#v\n", podAttachOptions))
170 if !podAttachOptions.Stdin || podAttachOptions.Container != "container1" {
171 return &v1.AdmissionResponse{Allowed: true}
172 }
173 return &v1.AdmissionResponse{
174 Allowed: false,
175 Result: &metav1.Status{
176 Message: "attaching to pod 'to-be-attached-pod' is not allowed",
177 },
178 }
179 }
180
View as plain text