1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package inject
17
18 import (
19 "context"
20 "encoding/json"
21 "fmt"
22 "strings"
23
24 v1 "k8s.io/api/admission/v1"
25
26 "github.com/chaos-mesh/chaos-mesh/pkg/annotation"
27 controllerCfg "github.com/chaos-mesh/chaos-mesh/pkg/config"
28 "github.com/chaos-mesh/chaos-mesh/pkg/metrics"
29 podselector "github.com/chaos-mesh/chaos-mesh/pkg/selector/pod"
30 "github.com/chaos-mesh/chaos-mesh/pkg/webhook/config"
31
32 ctrl "sigs.k8s.io/controller-runtime"
33 "sigs.k8s.io/controller-runtime/pkg/client"
34
35 corev1 "k8s.io/api/core/v1"
36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/apimachinery/pkg/types"
38 )
39
40 var log = ctrl.Log.WithName("inject-webhook")
41
42 var ignoredNamespaces = []string{
43 metav1.NamespaceSystem,
44 metav1.NamespacePublic,
45 }
46
47 const (
48
49 StatusInjected = "injected"
50 )
51
52
53 func Inject(res *v1.AdmissionRequest, cli client.Client, cfg *config.Config, controllerCfg *controllerCfg.ChaosControllerConfig, metrics *metrics.ChaosControllerManagerMetricsCollector) *v1.AdmissionResponse {
54 var pod corev1.Pod
55 if err := json.Unmarshal(res.Object.Raw, &pod); err != nil {
56 log.Error(err, "Could not unmarshal raw object")
57 return &v1.AdmissionResponse{
58 Result: &metav1.Status{
59 Message: err.Error(),
60 },
61 }
62 }
63
64
65 podName := potentialPodName(&pod.ObjectMeta)
66 if pod.ObjectMeta.Namespace == "" {
67 pod.ObjectMeta.Namespace = res.Namespace
68 }
69
70 log.Info("AdmissionReview for",
71 "Kind", res.Kind, "Namespace", res.Namespace, "Name", res.Name, "podName", podName, "UID", res.UID, "patchOperation", res.Operation, "UserInfo", res.UserInfo)
72 log.V(4).Info("Object", "Object", string(res.Object.Raw))
73 log.V(4).Info("OldObject", "OldObject", string(res.OldObject.Raw))
74 log.V(4).Info("Pod", "Pod", pod)
75
76 requiredKey, ok := injectRequired(&pod.ObjectMeta, cli, cfg, controllerCfg)
77 if !ok {
78 log.Info("Skipping injection due to policy check", "namespace", pod.ObjectMeta.Namespace, "name", podName)
79 return &v1.AdmissionResponse{
80 Allowed: true,
81 }
82 }
83
84 if metrics != nil {
85 metrics.InjectRequired.WithLabelValues(res.Namespace, requiredKey).Inc()
86 }
87 injectionConfig, err := cfg.GetRequestedConfig(pod.Namespace, requiredKey)
88 if err != nil {
89 log.Error(err, "Error getting injection config, permitting launch of pod with no sidecar injected", "injectionConfig",
90 injectionConfig)
91
92 return &v1.AdmissionResponse{
93 Allowed: true,
94 }
95 }
96
97 if injectionConfig.Selector != nil {
98 meet, err := podselector.CheckPodMeetSelector(pod, *injectionConfig.Selector)
99 if err != nil {
100 log.Error(err, "Failed to check pod selector", "namespace", pod.Namespace)
101 return &v1.AdmissionResponse{
102 Allowed: true,
103 }
104 }
105
106 if !meet {
107 log.Info("Skipping injection, this pod does not meet the selection criteria",
108 "namespace", pod.Namespace, "name", pod.Name)
109 return &v1.AdmissionResponse{
110 Allowed: true,
111 }
112 }
113 }
114
115 annotations := map[string]string{cfg.StatusAnnotationKey(): StatusInjected}
116
117 patchBytes, err := createPatch(&pod, injectionConfig, annotations)
118 if err != nil {
119 return &v1.AdmissionResponse{
120 Result: &metav1.Status{
121 Message: err.Error(),
122 },
123 }
124 }
125
126 log.Info("AdmissionResponse: patch", "patchBytes", string(patchBytes))
127 if metrics != nil {
128 metrics.Injections.WithLabelValues(res.Namespace, requiredKey).Inc()
129 }
130
131 patchType := v1.PatchTypeJSONPatch
132 return &v1.AdmissionResponse{
133 Allowed: true,
134 Patch: patchBytes,
135 PatchType: &patchType,
136 }
137 }
138
139
140 func injectRequired(metadata *metav1.ObjectMeta, cli client.Client, cfg *config.Config, controllerCfg *controllerCfg.ChaosControllerConfig) (string, bool) {
141
142 for _, namespace := range ignoredNamespaces {
143 if metadata.Namespace == namespace {
144 log.Info("Skip mutation for it' in special namespace", "name", metadata.Name, "namespace", metadata.Namespace)
145 return "", false
146 }
147 }
148
149 if controllerCfg.EnableFilterNamespace {
150 ok, err := podselector.IsAllowedNamespaces(context.Background(), cli, metadata.Namespace)
151 if err != nil {
152 log.Error(err, "fail to check whether this namespace should be injected", "namespace", metadata.Namespace)
153 }
154
155 if !ok {
156 log.Info("Skip mutation for it' in special namespace", "name", metadata.Name, "namespace", metadata.Namespace)
157 return "", false
158 }
159 }
160
161 log.V(4).Info("meta", "meta", metadata)
162
163 if checkInjectStatus(metadata, cfg) {
164 log.Info("Pod annotation indicates injection already satisfied, skipping",
165 "namespace", metadata.Namespace, "name", metadata.Name,
166 "annotationKey", cfg.StatusAnnotationKey(), "value", StatusInjected)
167 return "", false
168 }
169
170 requiredConfig, ok := injectByPodRequired(metadata, cfg)
171 if ok {
172 log.Info("Pod annotation requesting sidecar config",
173 "namespace", metadata.Namespace, "name", metadata.Name,
174 "annotation", cfg.RequestAnnotationKey(), "requiredConfig", requiredConfig)
175 return requiredConfig, true
176 }
177
178 requiredConfig, ok = injectByNamespaceRequired(metadata, cli, cfg)
179 if ok {
180 log.Info("Pod annotation requesting sidecar config",
181 "namespace", metadata.Namespace, "name", metadata.Name,
182 "annotation", cfg.RequestAnnotationKey(), "requiredConfig", requiredConfig)
183 return requiredConfig, true
184 }
185
186 requiredConfig, ok = injectByNamespaceInitRequired(metadata, cli, cfg)
187 if ok {
188 log.Info("Pod annotation init requesting sidecar config",
189 "namespace", metadata.Namespace, "name", metadata.Name,
190 "annotation", cfg.RequestAnnotationKey(), "requiredConfig", requiredConfig)
191 return requiredConfig, true
192 }
193
194 return "", false
195 }
196
197 func checkInjectStatus(metadata *metav1.ObjectMeta, cfg *config.Config) bool {
198 annotations := metadata.GetAnnotations()
199 if annotations == nil {
200 annotations = make(map[string]string)
201 }
202
203 status, ok := annotations[cfg.StatusAnnotationKey()]
204 if ok && strings.ToLower(status) == StatusInjected {
205 return true
206 }
207
208 return false
209 }
210
211 func injectByNamespaceRequired(metadata *metav1.ObjectMeta, cli client.Client, cfg *config.Config) (string, bool) {
212 var ns corev1.Namespace
213 if err := cli.Get(context.Background(), types.NamespacedName{Name: metadata.Namespace}, &ns); err != nil {
214 log.Error(err, "failed to get namespace", "namespace", metadata.Namespace)
215 return "", false
216 }
217 annotations := ns.GetAnnotations()
218 if annotations == nil {
219 annotations = make(map[string]string)
220 }
221
222 required, ok := annotations[annotation.GenKeyForWebhook(cfg.RequestAnnotationKey(), metadata.Name)]
223 if !ok {
224 log.Info("Pod annotation by namespace is missing, skipping injection",
225 "namespace", metadata.Namespace, "pod", metadata.Name, "config", required)
226 return "", false
227 }
228
229 log.Info("Get sidecar config from namespace annotations",
230 "namespace", metadata.Namespace, "pod", metadata.Name, "config", required)
231 return strings.ToLower(required), true
232 }
233
234 func injectByNamespaceInitRequired(metadata *metav1.ObjectMeta, cli client.Client, cfg *config.Config) (string, bool) {
235 var ns corev1.Namespace
236 if err := cli.Get(context.Background(), types.NamespacedName{Name: metadata.Namespace}, &ns); err != nil {
237 log.Error(err, "failed to get namespace", "namespace", metadata.Namespace)
238 return "", false
239 }
240
241 annotations := ns.GetAnnotations()
242 if annotations == nil {
243 annotations = make(map[string]string)
244 }
245
246 required, ok := annotations[cfg.RequestInitAnnotationKey()]
247 if !ok {
248 log.Info("Pod annotation by namespace is missing, skipping injection",
249 "namespace", metadata.Namespace, "pod", metadata.Name, "config", required)
250 return "", false
251 }
252
253 log.Info("Get sidecar config from namespace annotations",
254 "namespace", metadata.Namespace, "pod", metadata.Name, "config", required)
255 return strings.ToLower(required), true
256 }
257
258 func injectByPodRequired(metadata *metav1.ObjectMeta, cfg *config.Config) (string, bool) {
259 annotations := metadata.GetAnnotations()
260 if annotations == nil {
261 annotations = make(map[string]string)
262 }
263
264 required, ok := annotations[cfg.RequestAnnotationKey()]
265 if !ok {
266 log.Info("Pod annotation is missing, skipping injection",
267 "namespace", metadata.Namespace, "name", metadata.Name, "annotation", cfg.RequestAnnotationKey())
268 return "", false
269 }
270
271 log.Info("Get sidecar config from pod annotations",
272 "namespace", metadata.Namespace, "pod", metadata.Name, "config", required)
273 return strings.ToLower(required), true
274 }
275
276
277 func createPatch(pod *corev1.Pod, inj *config.InjectionConfig, annotations map[string]string) ([]byte, error) {
278 var patch []patchOperation
279
280
281
282 mutatedInjectedContainers := mergeEnvVars(inj.Environment, inj.Containers)
283 mutatedInjectedContainers = mergeVolumeMounts(inj.VolumeMounts, mutatedInjectedContainers)
284
285
286
287 mutatedInjectedInitContainers := mergeEnvVars(inj.Environment, inj.InitContainers)
288 mutatedInjectedInitContainers = mergeVolumeMounts(inj.VolumeMounts, mutatedInjectedInitContainers)
289
290
291 patch = append(patch, setVolumeMounts(pod.Spec.Containers, inj.VolumeMounts, "/spec/containers")...)
292
293
294 patch = append(patch, setEnvironment(pod.Spec.Containers, inj.Environment)...)
295
296
297 patch = append(patch, addContainers(pod.Spec.Containers, mutatedInjectedContainers, "/spec/containers")...)
298
299
300 patch = append(patch, addContainers(pod.Spec.InitContainers, mutatedInjectedInitContainers, "/spec/initContainers")...)
301 patch = append(patch, addHostAliases(pod.Spec.HostAliases, inj.HostAliases, "/spec/hostAliases")...)
302 patch = append(patch, addVolumes(pod.Spec.Volumes, inj.Volumes, "/spec/volumes")...)
303
304
305 patch = append(patch, updateAnnotations(pod.Annotations, annotations)...)
306
307
308 patch = append(patch, updateShareProcessNamespace(inj.ShareProcessNamespace)...)
309
310
311
312 patch = append(patch, setCommands(pod.Spec.Containers, inj.PostStart)...)
313
314 return json.Marshal(patch)
315 }
316
317 func setCommands(target []corev1.Container, postStart map[string]config.ExecAction) (patch []patchOperation) {
318 if postStart == nil {
319 return
320 }
321
322 for containerIndex, container := range target {
323 execCmd, ok := postStart[container.Name]
324 if !ok {
325 continue
326 }
327
328 path := fmt.Sprintf("/spec/containers/%d/command", containerIndex)
329
330 commands := MergeCommands(execCmd.Command, container.Command, container.Args)
331
332 log.Info("Inject command", "command", commands)
333
334 patch = append(patch, patchOperation{
335 Op: "replace",
336 Path: path,
337 Value: commands,
338 })
339
340 argsPath := fmt.Sprintf("/spec/containers/%d/args", containerIndex)
341 patch = append(patch, patchOperation{
342 Op: "replace",
343 Path: argsPath,
344 Value: []string{},
345 })
346 }
347 return patch
348 }
349
350 type patchOperation struct {
351 Op string `json:"op"`
352 Path string `json:"path"`
353 Value interface{} `json:"value,omitempty"`
354 }
355
356 func setEnvironment(target []corev1.Container, addedEnv []corev1.EnvVar) (patch []patchOperation) {
357 var value interface{}
358 for containerIndex, container := range target {
359
360 first := len(container.Env) == 0
361 for _, add := range addedEnv {
362 path := fmt.Sprintf("/spec/containers/%d/env", containerIndex)
363 hasKey := false
364
365 for _, origEnv := range container.Env {
366 if origEnv.Name == add.Name {
367 hasKey = true
368 break
369 }
370 }
371 if !hasKey {
372
373 value = add
374 if first {
375 first = false
376 value = []corev1.EnvVar{add}
377 } else {
378 path = path + "/-"
379 }
380 patch = append(patch, patchOperation{
381 Op: "add",
382 Path: path,
383 Value: value,
384 })
385 }
386 }
387 }
388
389 return patch
390 }
391
392 func addContainers(target, added []corev1.Container, basePath string) (patch []patchOperation) {
393 first := len(target) == 0
394 var value interface{}
395 for _, add := range added {
396 value = add
397 log.V(6).Info("Add container", "add", add)
398 path := basePath
399 if first {
400 first = false
401 value = []corev1.Container{add}
402 } else {
403 path = path + "/-"
404 }
405 patch = append(patch, patchOperation{
406 Op: "add",
407 Path: path,
408 Value: value,
409 })
410 }
411 return patch
412 }
413
414 func addVolumes(target, added []corev1.Volume, basePath string) (patch []patchOperation) {
415 first := len(target) == 0
416 var value interface{}
417 for _, add := range added {
418 value = add
419 path := basePath
420 if first {
421 first = false
422 value = []corev1.Volume{add}
423 } else {
424 path = path + "/-"
425 }
426 patch = append(patch, patchOperation{
427 Op: "add",
428 Path: path,
429 Value: value,
430 })
431 }
432 return patch
433 }
434
435 func setVolumeMounts(target []corev1.Container, addedVolumeMounts []corev1.VolumeMount, basePath string) (patch []patchOperation) {
436 for index, c := range target {
437 volumeMounts := map[string]corev1.VolumeMount{}
438 for _, vm := range c.VolumeMounts {
439 volumeMounts[vm.Name] = vm
440 }
441 for _, added := range addedVolumeMounts {
442 log.Info("volumeMount", "add", added)
443 volumeMounts[added.Name] = added
444 }
445
446 vs := []corev1.VolumeMount{}
447 for _, vm := range volumeMounts {
448 vs = append(vs, vm)
449 }
450 target[index].VolumeMounts = vs
451 }
452
453 patch = append(patch, patchOperation{
454 Op: "replace",
455 Path: basePath,
456 Value: target,
457 })
458
459 return patch
460 }
461
462 func addHostAliases(target, added []corev1.HostAlias, basePath string) (patch []patchOperation) {
463 first := len(target) == 0
464 var value interface{}
465 for _, add := range added {
466 value = add
467 path := basePath
468 if first {
469 first = false
470 value = []corev1.HostAlias{add}
471 } else {
472 path = path + "/-"
473 }
474 patch = append(patch, patchOperation{
475 Op: "add",
476 Path: path,
477 Value: value,
478 })
479 }
480 return patch
481 }
482
483
484
485
486 func mergeEnvVars(envs []corev1.EnvVar, containers []corev1.Container) []corev1.Container {
487 mutatedContainers := []corev1.Container{}
488 for _, c := range containers {
489 for _, newEnv := range envs {
490
491
492 skip := false
493 for _, origEnv := range c.Env {
494 if origEnv.Name == newEnv.Name {
495 skip = true
496 break
497 }
498 }
499 if !skip {
500 c.Env = append(c.Env, newEnv)
501 }
502 }
503 mutatedContainers = append(mutatedContainers, c)
504 }
505 return mutatedContainers
506 }
507
508 func mergeVolumeMounts(volumeMounts []corev1.VolumeMount, containers []corev1.Container) []corev1.Container {
509 mutatedContainers := []corev1.Container{}
510 for _, c := range containers {
511 for _, newVolumeMount := range volumeMounts {
512
513
514 skip := false
515 for _, origVolumeMount := range c.VolumeMounts {
516 if origVolumeMount.Name == newVolumeMount.Name {
517 skip = true
518 break
519 }
520 }
521 if !skip {
522 c.VolumeMounts = append(c.VolumeMounts, newVolumeMount)
523 }
524 }
525 mutatedContainers = append(mutatedContainers, c)
526 }
527 return mutatedContainers
528 }
529
530 func updateAnnotations(target map[string]string, added map[string]string) (patch []patchOperation) {
531 for key, value := range added {
532 if target == nil {
533 target = map[string]string{}
534 patch = append(patch, patchOperation{
535 Op: "add",
536 Path: "/metadata/annotations",
537 Value: map[string]string{
538 key: value,
539 },
540 })
541 } else {
542 op := "add"
543 if target[key] != "" {
544 op = "replace"
545 }
546 patch = append(patch, patchOperation{
547 Op: op,
548 Path: "/metadata/annotations/" + escapeJSONPointerValue(key),
549 Value: value,
550 })
551 }
552 }
553 return patch
554 }
555
556 func escapeJSONPointerValue(in string) string {
557 step := strings.Replace(in, "~", "~0", -1)
558 return strings.Replace(step, "/", "~1", -1)
559 }
560
561 func updateShareProcessNamespace(value bool) (patch []patchOperation) {
562 op := "add"
563 patch = append(patch, patchOperation{
564 Op: op,
565 Path: "/spec/shareProcessNamespace",
566 Value: value,
567 })
568 return patch
569 }
570
571 func potentialPodName(metadata *metav1.ObjectMeta) string {
572 if metadata.Name != "" {
573 return metadata.Name
574 }
575 if metadata.GenerateName != "" {
576 return metadata.GenerateName + "***** (actual name not yet known)"
577 }
578 return ""
579 }
580