package webhook import ( "context" "encoding/json" "fmt" "math/rand" "net/http" "time" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" apierrors "k8s.io/apimachinery/pkg/api/errors" ctrl "sigs.k8s.io/controller-runtime" ) func init() { rand.Seed(time.Now().UnixNano()) } func randomString(n int) string { var letters = []rune("abcdefghijklmnopqrstuvwxyz0123456789") s := make([]rune, n) for i := range s { s[i] = letters[rand.Intn(len(letters))] } return string(s) } var log = ctrl.Log.WithName("pod-mutator") // PodMutator mutates Pods type PodMutator struct { Client client.Client decoder *admission.Decoder TargetNS string TargetDSList []string } // PodMutator implements admission.DecoderInjector. // A decoder will be automatically injected. // InjectDecoder injects the decoder. func (a *PodMutator) InjectDecoder(d *admission.Decoder) error { a.decoder = d return nil } // Handle handles admission requests. func (a *PodMutator) Handle(ctx context.Context, req admission.Request) admission.Response { pod := &corev1.Pod{} err := a.decoder.Decode(req, pod) if err != nil { return admission.Errored(http.StatusBadRequest, err) } // Check namespace if pod.Namespace == "" { pod.Namespace = req.Namespace } if pod.Namespace != a.TargetNS { return admission.Allowed("not in target namespace") } // Check if it belongs to target DaemonSet isTarget := false dsName := "" for _, targetDS := range a.TargetDSList { for _, owner := range pod.OwnerReferences { if owner.Kind == "DaemonSet" && owner.Name == targetDS { isTarget = true dsName = targetDS break } } if isTarget { break } } if !isTarget { return admission.Allowed("not a target daemonset pod") } // Extract NodeName from affinity if not set (common for DaemonSets) nodeName := pod.Spec.NodeName if nodeName == "" && pod.Spec.Affinity != nil && pod.Spec.Affinity.NodeAffinity != nil && pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { for _, term := range pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms { for _, expr := range term.MatchFields { if expr.Key == "metadata.name" && expr.Operator == corev1.NodeSelectorOpIn && len(expr.Values) > 0 { nodeName = expr.Values[0] break } } } } // Requirement: Secret name is the same as pod name. // Since pod.Name is empty during CREATE, we must assign it. if pod.Name == "" { pod.Name = pod.GenerateName + randomString(5) } secretName := pod.Name // Add finalizer to ensure our controller can cleanup the secret before pod is gone pod.Finalizers = append(pod.Finalizers, "inject-ds-webhook.example.com/cleanup") log.Info("Mutating pod", "node", nodeName, "podName", pod.Name, "secretName", secretName, "ds", dsName) // Create Secret secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, Namespace: pod.Namespace, Labels: map[string]string{ "injected-by": "inject-ds-webhook", }, }, StringData: map[string]string{ "node-name": nodeName, "pod-name": pod.Name, "secret-data": fmt.Sprintf("unique-secret-for-%s-on-%s", pod.Name, nodeName), }, } err = a.Client.Create(ctx, secret) if err != nil && !apierrors.IsAlreadyExists(err) { return admission.Errored(http.StatusInternalServerError, err) } // Mutate Pod to add volume volumeName := "node-secret-volume" // Add volume pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{ Name: volumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ SecretName: secretName, }, }, }) // Add volume mount to all containers for i := range pod.Spec.Containers { pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, corev1.VolumeMount{ Name: volumeName, MountPath: "/etc/node-secret", ReadOnly: true, }) } marshaledPod, err := json.Marshal(pod) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod) }