From 4e0851868efa949f125dcb51c3e3b8bc77ca089a Mon Sep 17 00:00:00 2001 From: Bezalel Brandwine Date: Wed, 24 Nov 2021 15:23:50 +0200 Subject: [PATCH] initial host sensor deployment stage --- hostsensorutils/hostsensordeploy.go | 138 ++++++++++++++++++++++++++++ hostsensorutils/hostsensoryamls.go | 70 ++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 hostsensorutils/hostsensordeploy.go create mode 100644 hostsensorutils/hostsensoryamls.go diff --git a/hostsensorutils/hostsensordeploy.go b/hostsensorutils/hostsensordeploy.go new file mode 100644 index 00000000..0e072be5 --- /dev/null +++ b/hostsensorutils/hostsensordeploy.go @@ -0,0 +1,138 @@ +package hostsensorutils + +import ( + "encoding/json" + "fmt" + "io" + "strings" + "sync" + + "github.com/armosec/k8s-interface/k8sinterface" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apimachinery/pkg/watch" + appsapplyv1 "k8s.io/client-go/applyconfigurations/apps/v1" + coreapplyv1 "k8s.io/client-go/applyconfigurations/core/v1" +) + +type HostSensorHandler struct { + HostSensorNamespace string + HostSensorPort int32 + HostSensorDaemonSetName string + HostSensorPodNames map[string]string //map from pod names to node names + IsReady <-chan bool //readonly chan + k8sObj *k8sinterface.KubernetesApi + DaemonSet *appsv1.DaemonSet + podListLock sync.RWMutex +} + +type HostSensorDataEnvelope struct { + Kind string `json:"kind"` + NodeName string `json:"nodeName"` + Data []json.RawMessage `json:"data"` +} + +func NewHostSensorHandler(k8sObj *k8sinterface.KubernetesApi) (*HostSensorHandler, error) { + // deploy the YAML + // store namespace + port + // store pod names + // make sure all pods are running, after X seconds treat has running anyway, and log an error on the pods not running yet + // return the object + if k8sObj == nil { + return nil, fmt.Errorf("nil k8s interface received") + } + hsh := &HostSensorHandler{k8sObj: k8sObj} + if err := hsh.applyYAML(); err != nil { + return nil, fmt.Errorf("in NewHostSensorHandler, failed to apply YAML: %v", err) + } + return hsh, nil +} + +func (hsh *HostSensorHandler) applyYAML() error { + dec := yaml.NewDocumentDecoder(io.NopCloser(strings.NewReader(hostSensorYAML))) + // apply namespace + singleYAMLBytes := make([]byte, 0, 2048) + if _, err := dec.Read(singleYAMLBytes); err != nil { + return fmt.Errorf("failed to read YAML of namespace: %v", err) + } + namespaceAC := &coreapplyv1.NamespaceApplyConfiguration{} + if err := yaml.Unmarshal(singleYAMLBytes, namespaceAC); err != nil { + return fmt.Errorf("failed to Unmarshal YAML of namespace: %v", err) + } + + if ns, err := hsh.k8sObj.KubernetesClient.CoreV1().Namespaces().Apply(hsh.k8sObj.Context, namespaceAC, metav1.ApplyOptions{}); err != nil { + return fmt.Errorf("failed to apply YAML of namespace: %v", err) + } else { + hsh.HostSensorNamespace = ns.Name + } + // apply deamonset + deamonAC := &appsapplyv1.DaemonSetApplyConfiguration{} + singleYAMLBytes = make([]byte, 0, 4096) + if _, err := dec.Read(singleYAMLBytes); err != nil { + return fmt.Errorf("failed to read YAML of deamonset: %v", err) + } + if err := yaml.Unmarshal(singleYAMLBytes, deamonAC); err != nil { + return fmt.Errorf("failed to Unmarshal YAML of deamonset: %v", err) + } + deamonAC.Namespace = &hsh.HostSensorNamespace + if ds, err := hsh.k8sObj.KubernetesClient.AppsV1().DaemonSets(hsh.HostSensorNamespace).Apply(hsh.k8sObj.Context, deamonAC, metav1.ApplyOptions{}); err != nil { + return fmt.Errorf("failed to apply YAML of deamonset: %v", err) + } else { + hsh.HostSensorDaemonSetName = ds.Name + hsh.HostSensorPort = ds.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort + hsh.DaemonSet = ds + } + return nil +} + +func (hsh *HostSensorHandler) populatePodNamesToNodeNames() error { + + go func() { + watchRes, err := hsh.k8sObj.KubernetesClient.CoreV1().Pods(hsh.DaemonSet.Namespace).Watch(hsh.k8sObj.Context, metav1.ListOptions{ + Watch: true, + LabelSelector: fmt.Sprintf("app=%s", hsh.DaemonSet.Labels["app"]), + }) + if err != nil { + fmt.Printf("Failed to watch over daemonset pods") + } + for eve := range watchRes.ResultChan() { + pod, ok := eve.Object.(*corev1.Pod) + if !ok { + fmt.Printf("Failed to watch over daemonset pods: not a Pod") + continue + } + hsh.podListLock.Lock() + switch eve.Type { + case watch.Added: + if pod.Status.Phase == corev1.PodRunning { + hsh.HostSensorPodNames[pod.ObjectMeta.Name] = pod.Spec.NodeName + } else { + delete(hsh.HostSensorPodNames, pod.ObjectMeta.Name) + } + default: + delete(hsh.HostSensorPodNames, pod.ObjectMeta.Name) + } + hsh.podListLock.Unlock() + } + }() + + return nil +} + +func (hsh *HostSensorHandler) TearDown() error { + // remove the namespace + if err := hsh.k8sObj.KubernetesClient.CoreV1().Namespaces().Delete(hsh.k8sObj.Context, hsh.HostSensorNamespace, metav1.DeleteOptions{}); err != nil { + return fmt.Errorf("failed to delete host-sensor namespace: %v", err) + } + // TODO: wait for termination + + return nil +} + +// return list of +func (hsh *HostSensorHandler) GetKubeletConfigurations() ([][]byte, error) { + // loop over pods and port-forward it to each of them + return make([][]byte, 0), nil +} diff --git a/hostsensorutils/hostsensoryamls.go b/hostsensorutils/hostsensoryamls.go new file mode 100644 index 00000000..fec9c534 --- /dev/null +++ b/hostsensorutils/hostsensoryamls.go @@ -0,0 +1,70 @@ +package hostsensorutils + +const hostSensorYAML = ` +apiVersion: v1 +kind: Namespace +metadata: + labels: + app: host-sensor + kubernetes.io/metadata.name: armo-kube-host-sensor + tier: armo-kube-host-sensor-control-plane + name: armo-kube-host-sensor +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: host-sensor + namespace: armo-kube-host-sensor + labels: + k8s-app: armo-kube-host-sensor +spec: + selector: + matchLabels: + name: host-sensor + template: + metadata: + labels: + name: host-sensor + spec: + tolerations: + # this toleration is to have the daemonset runnable on master nodes + # remove it if your masters can't run pods + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + containers: + - name: host-sensor + image: quay.io/armosec/kube-host-sensor:latest + securityContext: + privileged: true + readOnlyRootFilesystem: true + procMount: Unmasked + ports: + - name: http + hostPort: 7888 + containerPort: 7888 + resources: + limits: + cpu: 1m + memory: 200Mi + requests: + cpu: 1m + memory: 200Mi + volumeMounts: + - mountPath: /host_fs + name: host-filesystem + terminationGracePeriodSeconds: 120 + dnsPolicy: ClusterFirstWithHostNet + automountServiceAccountToken: false + volumes: + - hostPath: + path: / + type: Directory + name: host-filesystem + hostNetwork: true + hostPID: true + hostIPC: true + + + + `