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