diff --git a/KubeArmor/BPF/anonmapexec.bpf.c b/KubeArmor/BPF/anonmapexec.bpf.c new file mode 100644 index 0000000000..3ce560a6cd --- /dev/null +++ b/KubeArmor/BPF/anonmapexec.bpf.c @@ -0,0 +1,126 @@ +// +build ignore +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2023 Authors of KubeArmor */ + +#include "shared.h" + +#define PROT_EXEC 0x4 /* page can be executed */ +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS + +#define S_IFIFO 0010000 + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 24); +} events SEC(".maps"); + +typedef struct { + u64 ts; + + u32 pid_id; + u32 mnt_id; + + u32 host_ppid; + u32 host_pid; + + u32 ppid; + u32 pid; + u32 uid; + + u32 event_id; + s64 retval; + + u8 comm[TASK_COMM_LEN]; + + unsigned long args[6]; +} mmap_event; + +static __always_inline u32 init_mmap_context(mmap_event *event_data) { + struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + + event_data->ts = bpf_ktime_get_ns(); + + event_data->host_ppid = get_task_ppid(task); + event_data->host_pid = bpf_get_current_pid_tgid() >> 32; + + u32 pid = get_task_ns_tgid(task); + if (event_data->host_pid == pid) { // host + event_data->pid_id = 0; + event_data->mnt_id = 0; + + event_data->ppid = get_task_ppid(task); + event_data->pid = bpf_get_current_pid_tgid() >> 32; + } else { // container + event_data->pid_id = get_task_pid_ns_id(task); + event_data->mnt_id = get_task_mnt_ns_id(task); + + event_data->ppid = get_task_ns_ppid(task); + event_data->pid = pid; + } + + event_data->uid = bpf_get_current_uid_gid(); + + // Clearing array to avoid garbage values + __builtin_memset(event_data->comm, 0, sizeof(event_data->comm)); + bpf_get_current_comm(&event_data->comm, sizeof(event_data->comm)); + + return 0; +} + + +// Force emitting struct mmap_event into the ELF. +const mmap_event *unused __attribute__((unused)); + +struct preset_map anon_map_exec_preset_containers SEC(".maps"); + + +SEC("lsm/mmap_file") +int BPF_PROG(enforce_mmap_file, struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags){ + + struct task_struct *t = (struct task_struct *)bpf_get_current_task(); + + struct outer_key okey; + get_outer_key(&okey, t); + + u32 *present = bpf_map_lookup_elem(&anon_map_exec_preset_containers, &okey); + + if (!present) { + return 0; + } + + // only if PROT_EXEC is assigned + if (prot & PROT_EXEC) { + if (flags & MAP_ANONYMOUS) { + mmap_event *event_data; + event_data = bpf_ringbuf_reserve(&events, sizeof(mmap_event), 0); + + if (!event_data) { + return 0; + } + + init_mmap_context(event_data); + + __builtin_memset(event_data->args, 0, sizeof(event_data->args)); + + event_data->args[0] = reqprot; + event_data->args[1] = prot; + event_data->args[2] = flags; + event_data->event_id = ANON_MAP_EXEC; + if (*present == BLOCK) { + event_data->retval = -EPERM; + } else { + event_data->retval = 0; + } + bpf_ringbuf_submit(event_data, 0); + // mapping not backed by any file with executable permission, denying mapping + if (*present == BLOCK) { + return -EPERM; + } else { + return 0; + } + } + } + return 0; +} \ No newline at end of file diff --git a/KubeArmor/BPF/filelessexec.bpf.c b/KubeArmor/BPF/filelessexec.bpf.c new file mode 100644 index 0000000000..919e920965 --- /dev/null +++ b/KubeArmor/BPF/filelessexec.bpf.c @@ -0,0 +1,111 @@ +// +build ignore +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2023 Authors of KubeArmor */ + +#include "shared.h" + + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 24); +} events SEC(".maps"); + + +// Force emitting struct mmap_event into the ELF. +const event *unused __attribute__((unused)); + +struct preset_map fileless_exec_preset_containers SEC(".maps"); + +#define MEMFD "memfd:" +#define RUN_SHM "/run/shm/" +#define DEV_SHM "/dev/shm/" + +static __always_inline int is_memfd(char *name) { + return string_prefix_match(name, MEMFD, sizeof(MEMFD)); +} + +static __always_inline int is_run_shm(char *name) { + return string_prefix_match(name, RUN_SHM, sizeof(RUN_SHM)); +} + +static __always_inline int is_dev_shm(char *name) { + return string_prefix_match(name, DEV_SHM, sizeof(DEV_SHM)); +} + +struct pathname { + char path[256]; +}; + +SEC("lsm/bprm_check_security") +int BPF_PROG(enforce_bprm_check_security, struct linux_binprm *bprm){ + + struct task_struct *t = (struct task_struct *)bpf_get_current_task(); + + struct outer_key okey; + get_outer_key(&okey, t); + + u32 *present = bpf_map_lookup_elem(&fileless_exec_preset_containers, &okey); + + if (!present) { + return 0; + } + + struct pathname path_data = {}; + + struct file *file = BPF_CORE_READ(bprm, file); + if (file == NULL) + return 0; + + bufs_t *path_buf = get_buf(PATH_BUFFER); + if (path_buf == NULL) + return 0; + + // prepend path is needed to capture /dev/shm and /run/shm paths + if (!prepend_path(&(file->f_path), path_buf)){ + // memfd files have no path in the filesystem -> extract their name + struct dentry *dentry = BPF_CORE_READ(&file->f_path, dentry); + struct qstr d_name = BPF_CORE_READ(dentry, d_name); + bpf_probe_read_kernel_str(path_data.path, MAX_STRING_SIZE, (void *) d_name.name); + } else { + u32 *path_offset = get_buf_off(PATH_BUFFER); + if (path_offset == NULL) + return 0; + + void *path_ptr = &path_buf->buf[*path_offset]; + bpf_probe_read_str(path_data.path, MAX_STRING_SIZE, path_ptr); + } + + const char *filename = BPF_CORE_READ(bprm, filename); + + if (is_memfd(path_data.path) || is_run_shm(path_data.path) || is_dev_shm(path_data.path)) { + event *event_data; + event_data = bpf_ringbuf_reserve(&events, sizeof(event), 0); + + if (!event_data) { + return 0; + } + + __builtin_memset(event_data->data.path, 0, sizeof(event_data->data.path)); + __builtin_memset(event_data->data.source, 0, sizeof(event_data->data.source)); + + bpf_probe_read_str(event_data->data.path, MAX_STRING_SIZE, path_data.path); + bpf_probe_read_str(event_data->data.source, MAX_STRING_SIZE, filename); + + init_context(event_data); + event_data->event_id = FILELESS_EXEC; + + // mapping not backed by any file with executable permission, denying mapping + if (*present == BLOCK) { + event_data->retval = -13; + bpf_ringbuf_submit(event_data, 0); + // bpf_printk("[bprm] fileless execution detected with pid %d, denying execution", event_data->pid); + return -13; + } else { + event_data->retval = 0; + bpf_ringbuf_submit(event_data, 0); + return 0; + } + } + + return 0; +} \ No newline at end of file diff --git a/KubeArmor/BPF/protectenv.bpf.c b/KubeArmor/BPF/protectenv.bpf.c new file mode 100644 index 0000000000..0c82e36500 --- /dev/null +++ b/KubeArmor/BPF/protectenv.bpf.c @@ -0,0 +1,86 @@ +// +build ignore +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2023 Authors of KubeArmor */ + +#include "shared.h" + +typedef struct { + u32 pid; + u32 pid_ns; + u32 mnt_ns; + char comm[80]; +} pevent; + +const pevent *unused __attribute__((unused)); + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 24); +} events SEC(".maps"); + +struct preset_map protectenv_preset_containers SEC(".maps"); + +#define DIR_PROC "/proc/" +#define FILE_ENVIRON "/environ" + +static __always_inline int isProcDir(char *path) { + return string_prefix_match(path, DIR_PROC, sizeof(DIR_PROC)); +} + +static __always_inline int isEnviron(char *path) { + return string_prefix_match(path, FILE_ENVIRON, sizeof(FILE_ENVIRON)); +} + +SEC("lsm/file_open") +int BPF_PROG(enforce_file, struct file *file) { + + struct task_struct *t = (struct task_struct *)bpf_get_current_task(); + + struct outer_key okey; + get_outer_key(&okey, t); + + u32 *present = bpf_map_lookup_elem(&protectenv_preset_containers, &okey); + + if (!present) { + return 0; + } + + u64 id = bpf_get_current_pid_tgid(); + u32 tgid = id >> 32; + + char path[80]; + bpf_d_path(&file->f_path, path, 80); + + if (!isProcDir(path)) { + return 0; + } + + long envpid; + int count = bpf_strtol(path + sizeof(DIR_PROC) - 1, 10, 0, &envpid); + if (count < 0) { + return 0; + } + u8 envstart = sizeof(DIR_PROC) + count - 1; + if (envstart < 80 && !isEnviron(path + envstart)) { + return 0; + } + + long pid = get_task_ns_tgid(t); + + if (envpid != pid) { + + pevent *task_info; + + task_info = bpf_ringbuf_reserve(&events, sizeof(pevent), 0); + if (!task_info) { + return 0; + } + task_info->pid = pid; + task_info->pid_ns = okey.pid_ns; + task_info->mnt_ns = okey.mnt_ns; + bpf_ringbuf_submit(task_info, 0); + return -EPERM; + } + + return 0; +} \ No newline at end of file diff --git a/KubeArmor/BPF/shared.h b/KubeArmor/BPF/shared.h index 2dbd0d4b8b..109b12647a 100644 --- a/KubeArmor/BPF/shared.h +++ b/KubeArmor/BPF/shared.h @@ -74,6 +74,40 @@ struct { __uint(max_entries, 3); } bufk SEC(".maps"); +// ============ +// match prefix +// ============ + +static __always_inline int string_prefix_match(const char *name, const char *prefix, size_t prefix_len) { + int i = 0; + while (i < prefix_len - 1 && name[i] != '\0' && name[i] == prefix[i]) { + i++; + } + return (i == prefix_len - 1) ? 1 : 0; +} + +// ============ +// == preset == +// ============ + +enum preset_action { + AUDIT = 1, + BLOCK +}; + +enum preset_type { + FILELESS_EXEC = 1001, + ANON_MAP_EXEC +}; + +struct preset_map { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 256); + __uint(key_size, sizeof(struct outer_key)); + __uint(value_size, sizeof(u32)); + __uint(pinning, LIBBPF_PIN_BY_NAME); +}; + typedef struct { u64 ts; @@ -172,8 +206,8 @@ static __always_inline bool prepend_path(struct path *path, bufs_t *string_p) { return false; } - struct dentry *dentry = path->dentry; - struct vfsmount *vfsmnt = path->mnt; + struct dentry *dentry = BPF_CORE_READ(path, dentry); + struct vfsmount *vfsmnt = BPF_CORE_READ(path, mnt); struct mount *mnt = real_mount(vfsmnt); @@ -183,7 +217,7 @@ static __always_inline bool prepend_path(struct path *path, bufs_t *string_p) { struct qstr d_name; #pragma unroll - for (int i = 0; i < 30; i++) { + for (int i = 0; i < 20; i++) { parent = BPF_CORE_READ(dentry, d_parent); mnt_root = BPF_CORE_READ(vfsmnt, mnt_root); diff --git a/KubeArmor/core/containerdHandler.go b/KubeArmor/core/containerdHandler.go index 66ca50db4d..ea24a0ce62 100644 --- a/KubeArmor/core/containerdHandler.go +++ b/KubeArmor/core/containerdHandler.go @@ -466,6 +466,9 @@ func (dm *KubeArmorDaemon) UpdateContainerdContainer(ctx context.Context, contai // update NsMap dm.SystemMonitor.AddContainerIDToNsMap(containerID, container.NamespaceName, container.PidNS, container.MntNS) dm.RuntimeEnforcer.RegisterContainer(containerID, container.PidNS, container.MntNS) + if dm.Presets != nil { + dm.Presets.RegisterContainer(container.ContainerID, container.PidNS, container.MntNS) + } if len(endPoint.SecurityPolicies) > 0 { // struct can be empty or no policies registered for the endPoint yet dm.Logger.UpdateSecurityPolicies("ADDED", endPoint) @@ -473,6 +476,10 @@ func (dm *KubeArmorDaemon) UpdateContainerdContainer(ctx context.Context, contai // enforce security policies dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) } + if dm.Presets != nil && endPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce preset rules + dm.Presets.UpdateSecurityPolicies(endPoint) + } } } @@ -538,6 +545,9 @@ func (dm *KubeArmorDaemon) UpdateContainerdContainer(ctx context.Context, contai // update NsMap dm.SystemMonitor.DeleteContainerIDFromNsMap(containerID, container.NamespaceName, container.PidNS, container.MntNS) dm.RuntimeEnforcer.UnregisterContainer(containerID) + if dm.Presets != nil { + dm.Presets.UnregisterContainer(containerID) + } } if cfg.GlobalCfg.StateAgent { diff --git a/KubeArmor/core/crioHandler.go b/KubeArmor/core/crioHandler.go index b1828da6c7..aaf0f0cb52 100644 --- a/KubeArmor/core/crioHandler.go +++ b/KubeArmor/core/crioHandler.go @@ -276,6 +276,9 @@ func (dm *KubeArmorDaemon) UpdateCrioContainer(ctx context.Context, containerID, // update NsMap dm.SystemMonitor.AddContainerIDToNsMap(containerID, container.NamespaceName, container.PidNS, container.MntNS) dm.RuntimeEnforcer.RegisterContainer(containerID, container.PidNS, container.MntNS) + if dm.Presets != nil { + dm.Presets.RegisterContainer(containerID, container.PidNS, container.MntNS) + } if len(endpoint.SecurityPolicies) > 0 { // struct can be empty or no policies registered for the endpoint yet dm.Logger.UpdateSecurityPolicies("ADDED", endpoint) @@ -283,6 +286,10 @@ func (dm *KubeArmorDaemon) UpdateCrioContainer(ctx context.Context, containerID, // enforce security policies dm.RuntimeEnforcer.UpdateSecurityPolicies(endpoint) } + if dm.Presets != nil && endpoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce preset rules + dm.Presets.UpdateSecurityPolicies(endpoint) + } } } diff --git a/KubeArmor/core/dockerHandler.go b/KubeArmor/core/dockerHandler.go index 35f3caab30..7628bd5efc 100644 --- a/KubeArmor/core/dockerHandler.go +++ b/KubeArmor/core/dockerHandler.go @@ -431,6 +431,9 @@ func (dm *KubeArmorDaemon) GetAlreadyDeployedDockerContainers() { // update NsMap dm.SystemMonitor.AddContainerIDToNsMap(container.ContainerID, container.NamespaceName, container.PidNS, container.MntNS) dm.RuntimeEnforcer.RegisterContainer(container.ContainerID, container.PidNS, container.MntNS) + if dm.Presets != nil { + dm.Presets.RegisterContainer(container.ContainerID, container.PidNS, container.MntNS) + } if len(endPoint.SecurityPolicies) > 0 { // struct can be empty or no policies registered for the endpoint yet dm.Logger.UpdateSecurityPolicies("ADDED", endPoint) @@ -438,6 +441,10 @@ func (dm *KubeArmorDaemon) GetAlreadyDeployedDockerContainers() { // enforce security policies dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) } + if dm.Presets != nil && endPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce preset rules + dm.Presets.UpdateSecurityPolicies(endPoint) + } } } @@ -616,6 +623,9 @@ func (dm *KubeArmorDaemon) UpdateDockerContainer(containerID, action string) { // update NsMap dm.SystemMonitor.AddContainerIDToNsMap(containerID, container.NamespaceName, container.PidNS, container.MntNS) dm.RuntimeEnforcer.RegisterContainer(containerID, container.PidNS, container.MntNS) + if dm.Presets != nil { + dm.Presets.RegisterContainer(containerID, container.PidNS, container.MntNS) + } if len(endPoint.SecurityPolicies) > 0 { // struct can be empty or no policies registered for the endpoint yet dm.Logger.UpdateSecurityPolicies("ADDED", endPoint) @@ -623,6 +633,11 @@ func (dm *KubeArmorDaemon) UpdateDockerContainer(containerID, action string) { // enforce security policies dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) } + + if dm.Presets != nil && endPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce preset rules + dm.Presets.UpdateSecurityPolicies(endPoint) + } } } @@ -700,6 +715,9 @@ func (dm *KubeArmorDaemon) UpdateDockerContainer(containerID, action string) { // update NsMap dm.SystemMonitor.DeleteContainerIDFromNsMap(containerID, container.NamespaceName, container.PidNS, container.MntNS) dm.RuntimeEnforcer.UnregisterContainer(containerID) + if dm.Presets != nil { + dm.Presets.UnregisterContainer(containerID) + } } dm.Logger.Printf("Detected a container (removed/%.12s)", containerID) diff --git a/KubeArmor/core/kubeArmor.go b/KubeArmor/core/kubeArmor.go index e86991de70..afecce924e 100644 --- a/KubeArmor/core/kubeArmor.go +++ b/KubeArmor/core/kubeArmor.go @@ -18,6 +18,7 @@ import ( cfg "github.com/kubearmor/KubeArmor/KubeArmor/config" kg "github.com/kubearmor/KubeArmor/KubeArmor/log" "github.com/kubearmor/KubeArmor/KubeArmor/policy" + "github.com/kubearmor/KubeArmor/KubeArmor/presets" "github.com/kubearmor/KubeArmor/KubeArmor/state" tp "github.com/kubearmor/KubeArmor/KubeArmor/types" "google.golang.org/grpc/health" @@ -27,10 +28,9 @@ import ( efc "github.com/kubearmor/KubeArmor/KubeArmor/enforcer" fd "github.com/kubearmor/KubeArmor/KubeArmor/feeder" - pb "github.com/kubearmor/KubeArmor/protobuf" - kvm "github.com/kubearmor/KubeArmor/KubeArmor/kvmAgent" mon "github.com/kubearmor/KubeArmor/KubeArmor/monitor" + pb "github.com/kubearmor/KubeArmor/protobuf" ) // ====================== // @@ -94,6 +94,9 @@ type KubeArmorDaemon struct { // runtime enforcer RuntimeEnforcer *efc.RuntimeEnforcer + // presets + Presets *presets.Preset + // kvm agent KVMAgent *kvm.KVMAgent @@ -302,6 +305,25 @@ func (dm *KubeArmorDaemon) CloseRuntimeEnforcer() bool { return true } +// ============= // +// == Presets == // +// ============= // + +// InitPresets Function +func (dm *KubeArmorDaemon) InitPresets(logger *fd.Feeder, monitor *mon.SystemMonitor) bool { + dm.Presets = presets.NewPreset(dm.Logger, dm.SystemMonitor) + return dm.Presets != nil +} + +// ClosePresets Function +func (dm *KubeArmorDaemon) ClosePresets() bool { + if err := dm.Presets.Destroy(); err != nil { + dm.Logger.Errf("Failed to destroy preset (%s)", err.Error()) + return false + } + return true +} + // =============== // // == KVM Agent == // // =============== // @@ -560,6 +582,13 @@ func KubeArmor() { dm.Logger.Print("Started to protect a host and containers") } } + + // initialize presets + if !dm.InitPresets(dm.Logger, dm.SystemMonitor) { + dm.Logger.Print("Disabled Presets since no presets are enabled") + } else { + dm.Logger.Print("Initialized Presets") + } } enableContainerPolicy := true diff --git a/KubeArmor/core/kubeUpdate.go b/KubeArmor/core/kubeUpdate.go index 18def91059..a7cfb5e5e9 100755 --- a/KubeArmor/core/kubeUpdate.go +++ b/KubeArmor/core/kubeUpdate.go @@ -352,10 +352,15 @@ func (dm *KubeArmorDaemon) UpdateEndPointWithPod(action string, pod tp.K8sPod) { // update security policies for _, endpoint := range endpoints { dm.Logger.UpdateSecurityPolicies(action, endpoint) - if dm.RuntimeEnforcer != nil && newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + if newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { // enforce security policies if !kl.ContainsElement(dm.SystemMonitor.UntrackedNamespaces, endpoint.NamespaceName) { - dm.RuntimeEnforcer.UpdateSecurityPolicies(endpoint) + if dm.RuntimeEnforcer != nil { + dm.RuntimeEnforcer.UpdateSecurityPolicies(endpoint) + } + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(endpoint) + } } else { dm.Logger.Warnf("Policy cannot be enforced in untracked namespace %s", endpoint.NamespaceName) } @@ -535,10 +540,15 @@ func (dm *KubeArmorDaemon) UpdateEndPointWithPod(action string, pod tp.K8sPod) { // update security policies dm.Logger.UpdateSecurityPolicies(action, endpoint) - if dm.RuntimeEnforcer != nil && endpoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + if endpoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { // enforce security policies if !kl.ContainsElement(dm.SystemMonitor.UntrackedNamespaces, endpoint.NamespaceName) { - dm.RuntimeEnforcer.UpdateSecurityPolicies(endpoint) + if dm.RuntimeEnforcer != nil { + dm.RuntimeEnforcer.UpdateSecurityPolicies(endpoint) + } + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(endpoint) + } } else { dm.Logger.Warnf("Policy cannot be enforced in untracked namespace %s", endpoint.NamespaceName) } @@ -1098,16 +1108,20 @@ func (dm *KubeArmorDaemon) UpdateSecurityPolicy(action string, secPolicyType str // update security policies dm.Logger.UpdateSecurityPolicies("UPDATED", endPoint) - if dm.RuntimeEnforcer != nil { - if endPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { - // enforce security policies - if !kl.ContainsElement(dm.SystemMonitor.UntrackedNamespaces, endPoint.NamespaceName) { - dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) - } else { - dm.Logger.Warnf("Policy cannot be enforced in untracked namespace %s", endPoint.NamespaceName) + if dm.EndPoints[idx].PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce security policies + if !kl.ContainsElement(dm.SystemMonitor.UntrackedNamespaces, dm.EndPoints[idx].NamespaceName) { + if dm.RuntimeEnforcer != nil { + dm.RuntimeEnforcer.UpdateSecurityPolicies(dm.EndPoints[idx]) } + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(dm.EndPoints[idx]) + } + } else { + dm.Logger.Warnf("Policy cannot be enforced in untracked namespace %s", dm.EndPoints[idx].NamespaceName) } } + } } } else if secPolicyType == KubeArmorClusterPolicy { @@ -1164,14 +1178,17 @@ func (dm *KubeArmorDaemon) UpdateSecurityPolicy(action string, secPolicyType str // update security policies dm.Logger.UpdateSecurityPolicies("UPDATED", endPoint) - if dm.RuntimeEnforcer != nil { - if endPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { - // enforce security policies - if !kl.ContainsElement(dm.SystemMonitor.UntrackedNamespaces, endPoint.NamespaceName) { - dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) - } else { - dm.Logger.Warnf("Policy cannot be enforced in untracked namespace %s", endPoint.NamespaceName) + if dm.EndPoints[idx].PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce security policies + if !kl.ContainsElement(dm.SystemMonitor.UntrackedNamespaces, dm.EndPoints[idx].NamespaceName) { + if dm.RuntimeEnforcer != nil { + dm.RuntimeEnforcer.UpdateSecurityPolicies(dm.EndPoints[idx]) + } + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(dm.EndPoints[idx]) } + } else { + dm.Logger.Warnf("Policy cannot be enforced in untracked namespace %s", dm.EndPoints[idx].NamespaceName) } } } diff --git a/KubeArmor/core/unorchestratedUpdates.go b/KubeArmor/core/unorchestratedUpdates.go index 13f3a0fce5..adb1332a44 100644 --- a/KubeArmor/core/unorchestratedUpdates.go +++ b/KubeArmor/core/unorchestratedUpdates.go @@ -103,9 +103,14 @@ func (dm *KubeArmorDaemon) MatchandUpdateContainerSecurityPolicies(cid string) { if cfg.GlobalCfg.Policy { // update security policies dm.Logger.UpdateSecurityPolicies("MODIFIED", ep) - if dm.RuntimeEnforcer != nil && ep.PolicyEnabled == tp.KubeArmorPolicyEnabled { - // enforce security policies - dm.RuntimeEnforcer.UpdateSecurityPolicies(ep) + if ep.PolicyEnabled == tp.KubeArmorPolicyEnabled { + if dm.RuntimeEnforcer != nil { + // enforce security policies + dm.RuntimeEnforcer.UpdateSecurityPolicies(ep) + } + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(ep) + } } } } @@ -207,9 +212,14 @@ func (dm *KubeArmorDaemon) handlePolicyEvent(eventType string, createEndPoint bo // update security policies dm.Logger.UpdateSecurityPolicies("ADDED", newPoint) - if dm.RuntimeEnforcer != nil && newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { - // enforce security policies - dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) + if newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + if dm.RuntimeEnforcer != nil { + // enforce security policies + dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) + } + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(newPoint) + } } } } else if eventType == "MODIFIED" { @@ -218,9 +228,15 @@ func (dm *KubeArmorDaemon) handlePolicyEvent(eventType string, createEndPoint bo // update security policies dm.Logger.UpdateSecurityPolicies("MODIFIED", newPoint) - if dm.RuntimeEnforcer != nil && newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { - // enforce security policies - dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) + if newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + if dm.RuntimeEnforcer != nil { + // enforce security policies + dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) + } + if dm.Presets != nil { + // enforce preset rules + dm.Presets.UpdateSecurityPolicies(newPoint) + } } } } else { // DELETED @@ -229,6 +245,9 @@ func (dm *KubeArmorDaemon) handlePolicyEvent(eventType string, createEndPoint bo dm.EndPoints[endpointIdx] = newPoint dm.Logger.UpdateSecurityPolicies("DELETED", newPoint) dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(newPoint) + } // delete endpoint if no containers or policies if len(newPoint.Containers) == 0 && len(newPoint.SecurityPolicies) == 0 { dm.EndPoints = append(dm.EndPoints[:endpointIdx], dm.EndPoints[endpointIdx+1:]...) @@ -652,7 +671,9 @@ func (dm *KubeArmorDaemon) ParseAndUpdateContainerSecurityPolicy(event tp.K8sKub dm.Logger.UpdateSecurityPolicies("DELETED", endPoint) endPoint.SecurityPolicies = append(endPoint.SecurityPolicies[:0], endPoint.SecurityPolicies[1:]...) dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) - + if dm.Presets != nil { + dm.Presets.UpdateSecurityPolicies(endPoint) + } endPoint = tp.EndPoint{} endPointIndex-- } else if len(endPoint.SecurityPolicies) >= 1 { @@ -669,9 +690,15 @@ func (dm *KubeArmorDaemon) ParseAndUpdateContainerSecurityPolicy(event tp.K8sKub // update security policies dm.Logger.UpdateSecurityPolicies("MODIFIED", endPoint) - if dm.RuntimeEnforcer != nil && endPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { - // enforce security policies - dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) + if endPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + if dm.RuntimeEnforcer != nil { + // enforce security policies + dm.RuntimeEnforcer.UpdateSecurityPolicies(endPoint) + } + if dm.Presets != nil { + // enforce preset rules + dm.Presets.UpdateSecurityPolicies(endPoint) + } } } } diff --git a/KubeArmor/enforcer/bpflsm/enforcer.go b/KubeArmor/enforcer/bpflsm/enforcer.go index 7c3fbc1c40..233cc02159 100644 --- a/KubeArmor/enforcer/bpflsm/enforcer.go +++ b/KubeArmor/enforcer/bpflsm/enforcer.go @@ -324,7 +324,7 @@ func (be *BPFEnforcer) TraceEvents() { containerID := "" if event.PidID != 0 && event.MntID != 0 { - containerID = be.Monitor.LookupContainerID(event.PidID, event.MntID, event.HostPPID, event.HostPID) + containerID = be.Monitor.LookupContainerID(event.PidID, event.MntID) } log := be.Monitor.BuildLogBase(event.EventID, mon.ContextCombined{ diff --git a/KubeArmor/enforcer/bpflsm/enforcer_bpfeb.o b/KubeArmor/enforcer/bpflsm/enforcer_bpfeb.o index 7d2cead750..002ccf421b 100644 Binary files a/KubeArmor/enforcer/bpflsm/enforcer_bpfeb.o and b/KubeArmor/enforcer/bpflsm/enforcer_bpfeb.o differ diff --git a/KubeArmor/enforcer/bpflsm/enforcer_bpfel.o b/KubeArmor/enforcer/bpflsm/enforcer_bpfel.o index af225eb2d5..e5d6ec8895 100644 Binary files a/KubeArmor/enforcer/bpflsm/enforcer_bpfel.o and b/KubeArmor/enforcer/bpflsm/enforcer_bpfel.o differ diff --git a/KubeArmor/enforcer/bpflsm/enforcer_path_bpfeb.o b/KubeArmor/enforcer/bpflsm/enforcer_path_bpfeb.o index 80ff9b63b1..8882370ced 100644 Binary files a/KubeArmor/enforcer/bpflsm/enforcer_path_bpfeb.o and b/KubeArmor/enforcer/bpflsm/enforcer_path_bpfeb.o differ diff --git a/KubeArmor/enforcer/bpflsm/enforcer_path_bpfel.o b/KubeArmor/enforcer/bpflsm/enforcer_path_bpfel.o index 0045eeffd0..48b6009050 100644 Binary files a/KubeArmor/enforcer/bpflsm/enforcer_path_bpfel.o and b/KubeArmor/enforcer/bpflsm/enforcer_path_bpfel.o differ diff --git a/KubeArmor/feeder/feeder.go b/KubeArmor/feeder/feeder.go index 60201056fe..8689af54da 100644 --- a/KubeArmor/feeder/feeder.go +++ b/KubeArmor/feeder/feeder.go @@ -534,7 +534,8 @@ func (fd *Feeder) PushLog(log tp.Log) { in case of enforcer = AppArmor only Default Posture logs will be converted to container/host log depending upon the defaultPostureLogs flag */ - if (cfg.GlobalCfg.EnforcerAlerts && fd.Enforcer == "BPFLSM" && log.Enforcer != "BPFLSM") || (fd.Enforcer != "BPFLSM" && !cfg.GlobalCfg.DefaultPostureLogs) { + + if (cfg.GlobalCfg.EnforcerAlerts && fd.Enforcer == "BPFLSM" && log.Enforcer == "eBPF Monitor") || (fd.Enforcer != "BPFLSM" && !cfg.GlobalCfg.DefaultPostureLogs) { log = fd.UpdateMatchedPolicy(log) if (log.Type == "MatchedPolicy" || log.Type == "MatchedHostPolicy") && ((fd.Enforcer == "BPFLSM" && (strings.Contains(log.PolicyName, "DefaultPosture") || !strings.Contains(log.Action, "Audit"))) || (fd.Enforcer != "BPFLSM" && strings.Contains(log.PolicyName, "DefaultPosture"))) { if log.Type == "MatchedPolicy" { @@ -545,7 +546,7 @@ func (fd *Feeder) PushLog(log tp.Log) { } } else { log = fd.UpdateMatchedPolicy(log) - if fd.Enforcer == "BPFLSM" { + if fd.Enforcer == "BPFLSM" && !strings.Contains(log.Enforcer, "PRESET") { log.Enforcer = "BPFLSM" } } @@ -553,6 +554,9 @@ func (fd *Feeder) PushLog(log tp.Log) { if log.Source == "" { // even if a log doesn't have a source, it must have a type if log.Type == "" { + if strings.Contains(log.Enforcer, "PRESET") { + kg.Printf("no source and type: %s\n", log.Enforcer) + } return } fd.Debug("Pushing Telemetry without source") diff --git a/KubeArmor/monitor/processTree.go b/KubeArmor/monitor/processTree.go index 0c39d49158..8f64ec34cf 100644 --- a/KubeArmor/monitor/processTree.go +++ b/KubeArmor/monitor/processTree.go @@ -21,7 +21,7 @@ import ( // ============================ // // LookupContainerID Function -func (mon *SystemMonitor) LookupContainerID(pidns, mntns, ppid, pid uint32) string { +func (mon *SystemMonitor) LookupContainerID(pidns, mntns uint32) string { key := NsKey{PidNS: pidns, MntNS: mntns} mon.NsMapLock.RLock() diff --git a/KubeArmor/monitor/systemMonitor.go b/KubeArmor/monitor/systemMonitor.go index 8a228a38c7..37a9402e93 100644 --- a/KubeArmor/monitor/systemMonitor.go +++ b/KubeArmor/monitor/systemMonitor.go @@ -655,7 +655,7 @@ func (mon *SystemMonitor) TraceSyscall() { containerID := "" if ctx.PidID != 0 && ctx.MntID != 0 { - containerID = mon.LookupContainerID(ctx.PidID, ctx.MntID, ctx.HostPPID, ctx.HostPID) + containerID = mon.LookupContainerID(ctx.PidID, ctx.MntID) if containerID == "" { time.Sleep(1 * time.Second) @@ -707,7 +707,7 @@ func (mon *SystemMonitor) TraceSyscall() { containerID := "" if ctx.PidID != 0 && ctx.MntID != 0 { - containerID = mon.LookupContainerID(ctx.PidID, ctx.MntID, ctx.HostPPID, ctx.HostPID) + containerID = mon.LookupContainerID(ctx.PidID, ctx.MntID) if containerID != "" { ContainersLock.RLock() diff --git a/KubeArmor/presets/anonmapexec/anonmapexec_bpfeb.go b/KubeArmor/presets/anonmapexec/anonmapexec_bpfeb.go new file mode 100644 index 0000000000..5aef59c4f9 --- /dev/null +++ b/KubeArmor/presets/anonmapexec/anonmapexec_bpfeb.go @@ -0,0 +1,150 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64 + +package anonmapexec + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type anonmapexecBufsK struct { + Path [256]int8 + Source [256]int8 +} + +type anonmapexecBufsT struct{ Buf [32768]int8 } + +// loadAnonmapexec returns the embedded CollectionSpec for anonmapexec. +func loadAnonmapexec() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_AnonmapexecBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load anonmapexec: %w", err) + } + + return spec, err +} + +// loadAnonmapexecObjects loads anonmapexec and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *anonmapexecObjects +// *anonmapexecPrograms +// *anonmapexecMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadAnonmapexecObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadAnonmapexec() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// anonmapexecSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type anonmapexecSpecs struct { + anonmapexecProgramSpecs + anonmapexecMapSpecs +} + +// anonmapexecSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type anonmapexecProgramSpecs struct { + EnforceMmapFile *ebpf.ProgramSpec `ebpf:"enforce_mmap_file"` +} + +// anonmapexecMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type anonmapexecMapSpecs struct { + AnonMapExecPresetContainers *ebpf.MapSpec `ebpf:"anon_map_exec_preset_containers"` + Bufk *ebpf.MapSpec `ebpf:"bufk"` + Bufs *ebpf.MapSpec `ebpf:"bufs"` + BufsOff *ebpf.MapSpec `ebpf:"bufs_off"` + Events *ebpf.MapSpec `ebpf:"events"` + KubearmorAlertThrottle *ebpf.MapSpec `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.MapSpec `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.MapSpec `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.MapSpec `ebpf:"kubearmor_events"` +} + +// anonmapexecObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadAnonmapexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type anonmapexecObjects struct { + anonmapexecPrograms + anonmapexecMaps +} + +func (o *anonmapexecObjects) Close() error { + return _AnonmapexecClose( + &o.anonmapexecPrograms, + &o.anonmapexecMaps, + ) +} + +// anonmapexecMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadAnonmapexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type anonmapexecMaps struct { + AnonMapExecPresetContainers *ebpf.Map `ebpf:"anon_map_exec_preset_containers"` + Bufk *ebpf.Map `ebpf:"bufk"` + Bufs *ebpf.Map `ebpf:"bufs"` + BufsOff *ebpf.Map `ebpf:"bufs_off"` + Events *ebpf.Map `ebpf:"events"` + KubearmorAlertThrottle *ebpf.Map `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.Map `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.Map `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.Map `ebpf:"kubearmor_events"` +} + +func (m *anonmapexecMaps) Close() error { + return _AnonmapexecClose( + m.AnonMapExecPresetContainers, + m.Bufk, + m.Bufs, + m.BufsOff, + m.Events, + m.KubearmorAlertThrottle, + m.KubearmorConfig, + m.KubearmorContainers, + m.KubearmorEvents, + ) +} + +// anonmapexecPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadAnonmapexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type anonmapexecPrograms struct { + EnforceMmapFile *ebpf.Program `ebpf:"enforce_mmap_file"` +} + +func (p *anonmapexecPrograms) Close() error { + return _AnonmapexecClose( + p.EnforceMmapFile, + ) +} + +func _AnonmapexecClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed anonmapexec_bpfeb.o +var _AnonmapexecBytes []byte diff --git a/KubeArmor/presets/anonmapexec/anonmapexec_bpfeb.o b/KubeArmor/presets/anonmapexec/anonmapexec_bpfeb.o new file mode 100644 index 0000000000..8c4ad67508 Binary files /dev/null and b/KubeArmor/presets/anonmapexec/anonmapexec_bpfeb.o differ diff --git a/KubeArmor/presets/anonmapexec/anonmapexec_bpfel.go b/KubeArmor/presets/anonmapexec/anonmapexec_bpfel.go new file mode 100644 index 0000000000..0d1330d6f5 --- /dev/null +++ b/KubeArmor/presets/anonmapexec/anonmapexec_bpfel.go @@ -0,0 +1,150 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 + +package anonmapexec + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type anonmapexecBufsK struct { + Path [256]int8 + Source [256]int8 +} + +type anonmapexecBufsT struct{ Buf [32768]int8 } + +// loadAnonmapexec returns the embedded CollectionSpec for anonmapexec. +func loadAnonmapexec() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_AnonmapexecBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load anonmapexec: %w", err) + } + + return spec, err +} + +// loadAnonmapexecObjects loads anonmapexec and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *anonmapexecObjects +// *anonmapexecPrograms +// *anonmapexecMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadAnonmapexecObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadAnonmapexec() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// anonmapexecSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type anonmapexecSpecs struct { + anonmapexecProgramSpecs + anonmapexecMapSpecs +} + +// anonmapexecSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type anonmapexecProgramSpecs struct { + EnforceMmapFile *ebpf.ProgramSpec `ebpf:"enforce_mmap_file"` +} + +// anonmapexecMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type anonmapexecMapSpecs struct { + AnonMapExecPresetContainers *ebpf.MapSpec `ebpf:"anon_map_exec_preset_containers"` + Bufk *ebpf.MapSpec `ebpf:"bufk"` + Bufs *ebpf.MapSpec `ebpf:"bufs"` + BufsOff *ebpf.MapSpec `ebpf:"bufs_off"` + Events *ebpf.MapSpec `ebpf:"events"` + KubearmorAlertThrottle *ebpf.MapSpec `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.MapSpec `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.MapSpec `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.MapSpec `ebpf:"kubearmor_events"` +} + +// anonmapexecObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadAnonmapexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type anonmapexecObjects struct { + anonmapexecPrograms + anonmapexecMaps +} + +func (o *anonmapexecObjects) Close() error { + return _AnonmapexecClose( + &o.anonmapexecPrograms, + &o.anonmapexecMaps, + ) +} + +// anonmapexecMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadAnonmapexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type anonmapexecMaps struct { + AnonMapExecPresetContainers *ebpf.Map `ebpf:"anon_map_exec_preset_containers"` + Bufk *ebpf.Map `ebpf:"bufk"` + Bufs *ebpf.Map `ebpf:"bufs"` + BufsOff *ebpf.Map `ebpf:"bufs_off"` + Events *ebpf.Map `ebpf:"events"` + KubearmorAlertThrottle *ebpf.Map `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.Map `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.Map `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.Map `ebpf:"kubearmor_events"` +} + +func (m *anonmapexecMaps) Close() error { + return _AnonmapexecClose( + m.AnonMapExecPresetContainers, + m.Bufk, + m.Bufs, + m.BufsOff, + m.Events, + m.KubearmorAlertThrottle, + m.KubearmorConfig, + m.KubearmorContainers, + m.KubearmorEvents, + ) +} + +// anonmapexecPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadAnonmapexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type anonmapexecPrograms struct { + EnforceMmapFile *ebpf.Program `ebpf:"enforce_mmap_file"` +} + +func (p *anonmapexecPrograms) Close() error { + return _AnonmapexecClose( + p.EnforceMmapFile, + ) +} + +func _AnonmapexecClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed anonmapexec_bpfel.o +var _AnonmapexecBytes []byte diff --git a/KubeArmor/presets/anonmapexec/anonmapexec_bpfel.o b/KubeArmor/presets/anonmapexec/anonmapexec_bpfel.o new file mode 100644 index 0000000000..e1299f8628 Binary files /dev/null and b/KubeArmor/presets/anonmapexec/anonmapexec_bpfel.o differ diff --git a/KubeArmor/presets/anonmapexec/preset.go b/KubeArmor/presets/anonmapexec/preset.go new file mode 100644 index 0000000000..60a3b68be7 --- /dev/null +++ b/KubeArmor/presets/anonmapexec/preset.go @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of KubeArmor + +package anonmapexec + +import ( + "bytes" + "encoding/binary" + "errors" + "log" + "os" + "strings" + "sync" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/ringbuf" + "github.com/cilium/ebpf/rlimit" + "github.com/kubearmor/KubeArmor/KubeArmor/presets/base" + + fd "github.com/kubearmor/KubeArmor/KubeArmor/feeder" + mon "github.com/kubearmor/KubeArmor/KubeArmor/monitor" + tp "github.com/kubearmor/KubeArmor/KubeArmor/types" +) + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang anonmapexec ../../BPF/anonmapexec.bpf.c -type mmap_event -no-global-types -- -I/usr/include/ -O2 -g + +const ( + NAME string = "AnonMapExecutionPreset" + EXISTS uint32 = 1024 +) + +type NsKey struct { + PidNS uint32 + MntNS uint32 +} + +type ContainerVal struct { + NsKey NsKey + Policy string +} + +type AnonMapExecPreset struct { + base.Preset + + BPFContainerMap *ebpf.Map + + // events + Events *ringbuf.Reader + EventsChannel chan []byte + + // ContainerID -> NsKey + ContainerMap map[string]ContainerVal + ContainerMapLock *sync.RWMutex + + Link link.Link + + obj anonmapexecObjects +} + +type MmapEvent struct { + Ts uint64 + + PidID uint32 + MntID uint32 + + HostPPID uint32 + HostPID uint32 + + PPID uint32 + PID uint32 + UID uint32 + + EventID int32 + + Retval int64 + + Comm [80]byte + + Args [6]uint64 +} + +func NewAnonMapExecPreset() *AnonMapExecPreset { + p := &AnonMapExecPreset{} + p.ContainerMap = make(map[string]ContainerVal) + p.ContainerMapLock = new(sync.RWMutex) + return p +} + +func (p *AnonMapExecPreset) Name() string { + return NAME +} + +func (p *AnonMapExecPreset) RegisterPreset(logger *fd.Feeder, monitor *mon.SystemMonitor) (base.PresetInterface, error) { + + if logger.Enforcer != "BPFLSM" { + // it's based on active enforcer, it might possible that node support bpflsm but + // current enforcer is not bpflsm + return nil, errors.New("AnonExecutionPreset not supported if bpflsm not supported") + } + + p.Logger = logger + p.Monitor = monitor + var err error + + if err = rlimit.RemoveMemlock(); err != nil { + p.Logger.Errf("Error removing rlimit %v", err) + return nil, err // Doesn't require clean up so not returning err + } + + p.Logger.Printf("Preset Pinpath: %s\n", monitor.PinPath) + + p.BPFContainerMap, _ = ebpf.NewMapWithOptions(&ebpf.MapSpec{ + Type: ebpf.Hash, + KeySize: 8, + ValueSize: 4, + MaxEntries: 256, + Pinning: ebpf.PinByName, + Name: "anon_map_exec_preset_containers", + }, ebpf.MapOptions{ + PinPath: monitor.PinPath, + }) + + if err := loadAnonmapexecObjects(&p.obj, &ebpf.CollectionOptions{ + Maps: ebpf.MapOptions{ + PinPath: monitor.PinPath, + }, + }); err != nil { + p.Logger.Errf("error loading BPF LSM objects: %v", err) + return nil, err + } + + p.Link, err = link.AttachLSM(link.LSMOptions{Program: p.obj.EnforceMmapFile}) + if err != nil { + p.Logger.Errf("opening lsm %s: %s", p.obj.EnforceMmapFile.String(), err) + return nil, err + } + + p.Events, err = ringbuf.NewReader(p.obj.Events) + if err != nil { + p.Logger.Errf("opening ringbuf reader: %s", err) + return nil, err + } + p.EventsChannel = make(chan []byte, mon.SyscallChannelSize) + + go p.TraceEvents() + + return p, nil + +} + +// TraceEvents traces events generated by bpflsm enforcer +func (p *AnonMapExecPreset) TraceEvents() { + + if p.Events == nil { + p.Logger.Err("ringbuf reader is nil, exiting trace events") + } + p.Logger.Print("Starting TraceEvents from AnonMapExec Presets") + go func() { + for { + + record, err := p.Events.Read() + if err != nil { + if errors.Is(err, ringbuf.ErrClosed) { + // This should only happen when we call DestroyMonitor while terminating the process. + // Adding a Warn just in case it happens at runtime, to help debug + p.Logger.Warnf("Ring Buffer closed, exiting TraceEvents %s", err.Error()) + return + } + p.Logger.Warnf("Ringbuf error reading %s", err.Error()) + continue + } + + p.EventsChannel <- record.RawSample + + } + }() + + for { + + dataRaw := <-p.EventsChannel + + var event MmapEvent + + if err := binary.Read(bytes.NewBuffer(dataRaw), binary.LittleEndian, &event); err != nil { + log.Printf("parsing ringbuf event: %s", err) + continue + } + + readLink := true + + containerID := "" + + if event.PidID != 0 && event.MntID != 0 { + containerID = p.Monitor.LookupContainerID(event.PidID, event.MntID) + } + + log := p.Monitor.BuildLogBase(event.EventID, mon.ContextCombined{ + ContainerID: containerID, + ContextSys: mon.SyscallContext{ + PID: event.PID, + PPID: event.PPID, + UID: event.UID, + + HostPID: event.HostPID, + HostPPID: event.HostPPID, + }, + }, readLink) + + if ckv, ok := p.ContainerMap[containerID]; ok { + log.PolicyName = ckv.Policy + log.Type = "MatchedPolicy" + } + + var f []string + f = append(f, ParseProtectionFlags(event.Args[0])) + f = append(f, ParseProtectionFlags(event.Args[1])) + f = append(f, ParseMemoryFlags(event.Args[2])) + if len(f) > 0 { + log.Data = strings.Join(f, ",") + } + log.Operation = "File" + if event.Retval >= 0 { + log.Result = "Passed" + } else { + log.Result = "Permission denied" + } + log.Enforcer = base.PRESET_ENFORCER + NAME + p.Logger.PushLog(log) + + } +} + +func (p *AnonMapExecPreset) RegisterContainer(containerID string, pidns, mntns uint32) { + ckv := NsKey{PidNS: pidns, MntNS: mntns} + + p.ContainerMapLock.Lock() + defer p.ContainerMapLock.Unlock() + p.Logger.Printf("[AnonMapExec] Registered container with id: %s\n", containerID) + p.ContainerMap[containerID] = ContainerVal{NsKey: ckv} +} + +func (p *AnonMapExecPreset) UnregisterContainer(containerID string) { + p.ContainerMapLock.Lock() + defer p.ContainerMapLock.Unlock() + + if val, ok := p.ContainerMap[containerID]; ok { + if err := p.DeleteContainerIDFromMap(containerID, val.NsKey); err != nil { + p.Logger.Errf("error deleting container %s: %s", containerID, err.Error()) + return + } + p.Logger.Printf("[AnonMapExec] Unregistered container with id: %s\n", containerID) + delete(p.ContainerMap, containerID) + } +} + +func (p *AnonMapExecPreset) AddContainerIDToMap(id string, ckv NsKey, action string) error { + p.Logger.Printf("[AnonMapExec] adding container with id to anon_map exec map: %s\n", id) + a := base.Block + if action == "Audit" { + a = base.Audit + } + if err := p.BPFContainerMap.Put(ckv, a); err != nil { + p.Logger.Errf("error adding container %s to outer map: %s", id, err) + return err + } + return nil +} + +func (p *AnonMapExecPreset) DeleteContainerIDFromMap(id string, ckv NsKey) error { + p.Logger.Printf("[AnonMapExec] deleting container with id to anon_map exec map: %s\n", id) + if err := p.BPFContainerMap.Delete(ckv); err != nil { + if !errors.Is(err, os.ErrNotExist) { + p.Logger.Errf("error deleting container %s in anon_map_exec_preset_containers map: %s", id, err.Error()) + return err + } + } + return nil +} + +func (p *AnonMapExecPreset) UpdateSecurityPolicies(endPoint tp.EndPoint) { + var anonMapExecPresetRulePresent bool + for _, cid := range endPoint.Containers { + anonMapExecPresetRulePresent = false + p.Logger.Printf("Updating container preset rules for %s", cid) + for _, secPolicy := range endPoint.SecurityPolicies { + for _, preset := range secPolicy.Spec.Presets { + if preset == tp.AnonMapExec { + anonMapExecPresetRulePresent = true + p.ContainerMapLock.RLock() + // Check if Container ID is registered in Map or not + ckv, ok := p.ContainerMap[cid] + if !ok { + // It maybe possible that CRI has unregistered the containers but K8s construct still has not sent this update while the policy was being applied, + // so the need to check if the container is present in the map before we apply policy. + p.ContainerMapLock.RUnlock() + return + } + ckv.Policy = secPolicy.Metadata["policyName"] + p.ContainerMap[cid] = ckv + err := p.AddContainerIDToMap(cid, ckv.NsKey, secPolicy.Spec.Action) + if err != nil { + p.Logger.Warnf("updating policy for container %s :%s ", cid, err) + } + p.ContainerMapLock.RUnlock() + } + } + } + if !anonMapExecPresetRulePresent { + p.ContainerMapLock.RLock() + ckv := p.ContainerMap[cid] + _ = p.DeleteContainerIDFromMap(cid, ckv.NsKey) + p.ContainerMapLock.RUnlock() + } + } +} + +func (p *AnonMapExecPreset) Destroy() error { + if p == nil { + return nil + } + var errBPFCleanUp error + + if err := p.obj.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + + if err := p.Link.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + + p.ContainerMapLock.Lock() + + if p.BPFContainerMap != nil { + if err := p.BPFContainerMap.Unpin(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + if err := p.BPFContainerMap.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + } + + p.ContainerMapLock.Unlock() + + if p.Events != nil { + if err := p.Events.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + } + + p = nil + return errBPFCleanUp +} diff --git a/KubeArmor/presets/anonmapexec/utils.go b/KubeArmor/presets/anonmapexec/utils.go new file mode 100644 index 0000000000..29566e4bb6 --- /dev/null +++ b/KubeArmor/presets/anonmapexec/utils.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of KubeArmor + +package anonmapexec + +import "strings" + +func ParseProtectionFlags(prot uint64) string { + var f []string + + if prot&0x1 == 0x1 { + f = append(f, "PROT_READ") + } + if prot&0x2 == 0x2 { + f = append(f, "PROT_WRITE") + } + if prot&0x4 == 0x4 { + f = append(f, "PROT_EXEC") + } + + return strings.Join(f, "|") +} + +/* +MAP_PRIVATE (0x02): Create a private copy-on-write mapping. +MAP_SHARED (0x01): Share this mapping with all processes that map this object. +MAP_ANONYMOUS (0x20): The mapping is not backed by any file. +MAP_FIXED (0x10): Interpret addr exactly as specified. +MAP_GROWSDOWN (0x1000): Used for stack-like regions. +MAP_DENYWRITE (0x0800): Prevent other processes from writing to this object. +*/ + +func ParseMemoryFlags(flag uint64) string { + var f []string + + if flag&0x01 == 0x01 { + f = append(f, "MAP_SHARED") + } + if flag&0x02 == 0x02 { + f = append(f, "MAP_PRIVATE") + } + if flag&0x10 == 0x10 { + f = append(f, "MAP_FIXED") + } + if flag&0x20 == 0x20 { + f = append(f, "MAP_ANONYMOUS") + } + if flag&0x1000 == 0x1000 { + f = append(f, "MAP_GROWSDOWN") + } + if flag&0x0800 == 0x0800 { + f = append(f, "MAP_DENYWRITE") + } + return strings.Join(f, "|") +} diff --git a/KubeArmor/presets/base/basePreset.go b/KubeArmor/presets/base/basePreset.go new file mode 100644 index 0000000000..d0ef322914 --- /dev/null +++ b/KubeArmor/presets/base/basePreset.go @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of KubeArmor + +// Package base provides interface for presets +package base + +import ( + fd "github.com/kubearmor/KubeArmor/KubeArmor/feeder" + mon "github.com/kubearmor/KubeArmor/KubeArmor/monitor" + tp "github.com/kubearmor/KubeArmor/KubeArmor/types" +) + +const ( + // PRESET_ENFORCER prefix for a preset + PRESET_ENFORCER string = "PRESET-" +) + +// PresetType represents type of a preset +type PresetType uint8 + +const ( + // FilelessExec preset type + FilelessExec PresetType = 1 + // AnonMapExec preset type + AnonMapExec PresetType = 2 +) + +// PresetAction preset action +type PresetAction uint32 + +const ( + // Audit action + Audit PresetAction = 1 + // Block action + Block PresetAction = 2 +) + +// Preset type +type Preset struct { + Logger *fd.Feeder + Monitor *mon.SystemMonitor +} + +// InnerKey type +type InnerKey struct { + Path [256]byte + Source [256]byte +} + +// EventPreset type +type EventPreset struct { + Ts uint64 + + PidID uint32 + MntID uint32 + + HostPPID uint32 + HostPID uint32 + + PPID uint32 + PID uint32 + UID uint32 + + EventID int32 + + Retval int64 + + Comm [80]byte + + Data InnerKey +} + +// PresetInterface interface +type PresetInterface interface { + Name() string + // Init() error + RegisterPreset(logger *fd.Feeder, monitor *mon.SystemMonitor) (PresetInterface, error) + RegisterContainer(containerID string, pidns, mntns uint32) + UnregisterContainer(containerID string) + UpdateSecurityPolicies(endPoint tp.EndPoint) + Destroy() error +} diff --git a/KubeArmor/presets/filelessexec/filelessexec_bpfeb.go b/KubeArmor/presets/filelessexec/filelessexec_bpfeb.go new file mode 100644 index 0000000000..5379d6aa7c --- /dev/null +++ b/KubeArmor/presets/filelessexec/filelessexec_bpfeb.go @@ -0,0 +1,150 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64 + +package filelessexec + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type filelessexecBufsK struct { + Path [256]int8 + Source [256]int8 +} + +type filelessexecBufsT struct{ Buf [32768]int8 } + +// loadFilelessexec returns the embedded CollectionSpec for filelessexec. +func loadFilelessexec() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_FilelessexecBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load filelessexec: %w", err) + } + + return spec, err +} + +// loadFilelessexecObjects loads filelessexec and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *filelessexecObjects +// *filelessexecPrograms +// *filelessexecMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadFilelessexecObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadFilelessexec() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// filelessexecSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type filelessexecSpecs struct { + filelessexecProgramSpecs + filelessexecMapSpecs +} + +// filelessexecSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type filelessexecProgramSpecs struct { + EnforceBprmCheckSecurity *ebpf.ProgramSpec `ebpf:"enforce_bprm_check_security"` +} + +// filelessexecMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type filelessexecMapSpecs struct { + Bufk *ebpf.MapSpec `ebpf:"bufk"` + Bufs *ebpf.MapSpec `ebpf:"bufs"` + BufsOff *ebpf.MapSpec `ebpf:"bufs_off"` + Events *ebpf.MapSpec `ebpf:"events"` + FilelessExecPresetContainers *ebpf.MapSpec `ebpf:"fileless_exec_preset_containers"` + KubearmorAlertThrottle *ebpf.MapSpec `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.MapSpec `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.MapSpec `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.MapSpec `ebpf:"kubearmor_events"` +} + +// filelessexecObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadFilelessexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type filelessexecObjects struct { + filelessexecPrograms + filelessexecMaps +} + +func (o *filelessexecObjects) Close() error { + return _FilelessexecClose( + &o.filelessexecPrograms, + &o.filelessexecMaps, + ) +} + +// filelessexecMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadFilelessexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type filelessexecMaps struct { + Bufk *ebpf.Map `ebpf:"bufk"` + Bufs *ebpf.Map `ebpf:"bufs"` + BufsOff *ebpf.Map `ebpf:"bufs_off"` + Events *ebpf.Map `ebpf:"events"` + FilelessExecPresetContainers *ebpf.Map `ebpf:"fileless_exec_preset_containers"` + KubearmorAlertThrottle *ebpf.Map `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.Map `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.Map `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.Map `ebpf:"kubearmor_events"` +} + +func (m *filelessexecMaps) Close() error { + return _FilelessexecClose( + m.Bufk, + m.Bufs, + m.BufsOff, + m.Events, + m.FilelessExecPresetContainers, + m.KubearmorAlertThrottle, + m.KubearmorConfig, + m.KubearmorContainers, + m.KubearmorEvents, + ) +} + +// filelessexecPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadFilelessexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type filelessexecPrograms struct { + EnforceBprmCheckSecurity *ebpf.Program `ebpf:"enforce_bprm_check_security"` +} + +func (p *filelessexecPrograms) Close() error { + return _FilelessexecClose( + p.EnforceBprmCheckSecurity, + ) +} + +func _FilelessexecClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed filelessexec_bpfeb.o +var _FilelessexecBytes []byte diff --git a/KubeArmor/presets/filelessexec/filelessexec_bpfeb.o b/KubeArmor/presets/filelessexec/filelessexec_bpfeb.o new file mode 100644 index 0000000000..4b7e39445c Binary files /dev/null and b/KubeArmor/presets/filelessexec/filelessexec_bpfeb.o differ diff --git a/KubeArmor/presets/filelessexec/filelessexec_bpfel.go b/KubeArmor/presets/filelessexec/filelessexec_bpfel.go new file mode 100644 index 0000000000..b1e912db25 --- /dev/null +++ b/KubeArmor/presets/filelessexec/filelessexec_bpfel.go @@ -0,0 +1,150 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 + +package filelessexec + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type filelessexecBufsK struct { + Path [256]int8 + Source [256]int8 +} + +type filelessexecBufsT struct{ Buf [32768]int8 } + +// loadFilelessexec returns the embedded CollectionSpec for filelessexec. +func loadFilelessexec() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_FilelessexecBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load filelessexec: %w", err) + } + + return spec, err +} + +// loadFilelessexecObjects loads filelessexec and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *filelessexecObjects +// *filelessexecPrograms +// *filelessexecMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadFilelessexecObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadFilelessexec() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// filelessexecSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type filelessexecSpecs struct { + filelessexecProgramSpecs + filelessexecMapSpecs +} + +// filelessexecSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type filelessexecProgramSpecs struct { + EnforceBprmCheckSecurity *ebpf.ProgramSpec `ebpf:"enforce_bprm_check_security"` +} + +// filelessexecMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type filelessexecMapSpecs struct { + Bufk *ebpf.MapSpec `ebpf:"bufk"` + Bufs *ebpf.MapSpec `ebpf:"bufs"` + BufsOff *ebpf.MapSpec `ebpf:"bufs_off"` + Events *ebpf.MapSpec `ebpf:"events"` + FilelessExecPresetContainers *ebpf.MapSpec `ebpf:"fileless_exec_preset_containers"` + KubearmorAlertThrottle *ebpf.MapSpec `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.MapSpec `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.MapSpec `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.MapSpec `ebpf:"kubearmor_events"` +} + +// filelessexecObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadFilelessexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type filelessexecObjects struct { + filelessexecPrograms + filelessexecMaps +} + +func (o *filelessexecObjects) Close() error { + return _FilelessexecClose( + &o.filelessexecPrograms, + &o.filelessexecMaps, + ) +} + +// filelessexecMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadFilelessexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type filelessexecMaps struct { + Bufk *ebpf.Map `ebpf:"bufk"` + Bufs *ebpf.Map `ebpf:"bufs"` + BufsOff *ebpf.Map `ebpf:"bufs_off"` + Events *ebpf.Map `ebpf:"events"` + FilelessExecPresetContainers *ebpf.Map `ebpf:"fileless_exec_preset_containers"` + KubearmorAlertThrottle *ebpf.Map `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.Map `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.Map `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.Map `ebpf:"kubearmor_events"` +} + +func (m *filelessexecMaps) Close() error { + return _FilelessexecClose( + m.Bufk, + m.Bufs, + m.BufsOff, + m.Events, + m.FilelessExecPresetContainers, + m.KubearmorAlertThrottle, + m.KubearmorConfig, + m.KubearmorContainers, + m.KubearmorEvents, + ) +} + +// filelessexecPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadFilelessexecObjects or ebpf.CollectionSpec.LoadAndAssign. +type filelessexecPrograms struct { + EnforceBprmCheckSecurity *ebpf.Program `ebpf:"enforce_bprm_check_security"` +} + +func (p *filelessexecPrograms) Close() error { + return _FilelessexecClose( + p.EnforceBprmCheckSecurity, + ) +} + +func _FilelessexecClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed filelessexec_bpfel.o +var _FilelessexecBytes []byte diff --git a/KubeArmor/presets/filelessexec/filelessexec_bpfel.o b/KubeArmor/presets/filelessexec/filelessexec_bpfel.o new file mode 100644 index 0000000000..7a7f079526 Binary files /dev/null and b/KubeArmor/presets/filelessexec/filelessexec_bpfel.o differ diff --git a/KubeArmor/presets/filelessexec/preset.go b/KubeArmor/presets/filelessexec/preset.go new file mode 100644 index 0000000000..b9e2d2ef9b --- /dev/null +++ b/KubeArmor/presets/filelessexec/preset.go @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of KubeArmor + +// Package filelessexec ... +package filelessexec + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang filelessexec ../../BPF/filelessexec.bpf.c -type event -no-global-types -- -I/usr/include/ -O2 -g + +import ( + "bytes" + "encoding/binary" + "errors" + "log" + "os" + "sync" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/ringbuf" + "github.com/cilium/ebpf/rlimit" + "github.com/kubearmor/KubeArmor/KubeArmor/presets/base" + + fd "github.com/kubearmor/KubeArmor/KubeArmor/feeder" + mon "github.com/kubearmor/KubeArmor/KubeArmor/monitor" + tp "github.com/kubearmor/KubeArmor/KubeArmor/types" +) + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang filelessexec ../../BPF/filelessexec.bpf.c -type event -no-global-types -- -I/usr/include/ -O2 -g + +const ( + // NAME of preset + NAME string = "FilelessExecutionPreset" +) + +// NsKey struct +type NsKey struct { + PidNS uint32 + MntNS uint32 +} + +// ContainerVal struct +type ContainerVal struct { + NsKey NsKey + Policy string +} + +// Preset struct +type Preset struct { + base.Preset + + BPFContainerMap *ebpf.Map + + // events + Events *ringbuf.Reader + EventsChannel chan []byte + + // ContainerID -> NsKey + ContainerMap map[string]ContainerVal + ContainerMapLock *sync.RWMutex + + Link link.Link + + obj filelessexecObjects +} + +// NewFilelessExecPreset creates an instance of FilelessExec Preset +func NewFilelessExecPreset() *Preset { + p := &Preset{} + p.ContainerMap = make(map[string]ContainerVal) + p.ContainerMapLock = new(sync.RWMutex) + return p +} + +// Name returns name of Preset +func (p *Preset) Name() string { + return NAME +} + +// RegisterPreset register FilelessExec preset +func (p *Preset) RegisterPreset(logger *fd.Feeder, monitor *mon.SystemMonitor) (base.PresetInterface, error) { + + if logger.Enforcer != "BPFLSM" { + // it's based on active enforcer, it might possible that node support bpflsm but + // current enforcer is not bpflsm + return nil, errors.New("FilelessExecutionPreset not supported if bpflsm not supported") + } + + p.Logger = logger + p.Monitor = monitor + var err error + + if err = rlimit.RemoveMemlock(); err != nil { + p.Logger.Errf("Error removing rlimit %v", err) + return nil, err // Doesn't require clean up so not returning err + } + + p.Logger.Printf("Preset Pinpath: %s\n", monitor.PinPath) + + p.BPFContainerMap, _ = ebpf.NewMapWithOptions(&ebpf.MapSpec{ + Type: ebpf.Hash, + KeySize: 8, + ValueSize: 4, + MaxEntries: 256, + Pinning: ebpf.PinByName, + Name: "fileless_exec_preset_containers", + }, ebpf.MapOptions{ + PinPath: monitor.PinPath, + }) + + if err := loadFilelessexecObjects(&p.obj, &ebpf.CollectionOptions{ + Maps: ebpf.MapOptions{ + PinPath: monitor.PinPath, + }, + }); err != nil { + p.Logger.Errf("error loading BPF LSM objects: %v", err) + return nil, err + } + + p.Link, err = link.AttachLSM(link.LSMOptions{Program: p.obj.EnforceBprmCheckSecurity}) + if err != nil { + p.Logger.Errf("opening lsm %s: %s", p.obj.EnforceBprmCheckSecurity.String(), err) + return nil, err + } + + p.Events, err = ringbuf.NewReader(p.obj.Events) + if err != nil { + p.Logger.Errf("opening ringbuf reader: %s", err) + return nil, err + } + p.EventsChannel = make(chan []byte, mon.SyscallChannelSize) + + go p.TraceEvents() + + return p, nil + +} + +// TraceEvents traces events generated by bpflsm enforcer +func (p *Preset) TraceEvents() { + + if p.Events == nil { + p.Logger.Err("ringbuf reader is nil, exiting trace events") + } + p.Logger.Print("Starting TraceEvents from FilelessExec Presets") + go func() { + for { + + record, err := p.Events.Read() + if err != nil { + if errors.Is(err, ringbuf.ErrClosed) { + // This should only happen when we call DestroyMonitor while terminating the process. + // Adding a Warn just in case it happens at runtime, to help debug + p.Logger.Warnf("Ring Buffer closed, exiting TraceEvents %s", err.Error()) + return + } + p.Logger.Warnf("Ringbuf error reading %s", err.Error()) + continue + } + + p.EventsChannel <- record.RawSample + + } + }() + + for { + + dataRaw := <-p.EventsChannel + + var event base.EventPreset + + if err := binary.Read(bytes.NewBuffer(dataRaw), binary.LittleEndian, &event); err != nil { + log.Printf("parsing ringbuf event: %s", err) + continue + } + + readLink := true + + containerID := "" + + if event.PidID != 0 && event.MntID != 0 { + containerID = p.Monitor.LookupContainerID(event.PidID, event.MntID) + } + + log := p.Monitor.BuildLogBase(event.EventID, mon.ContextCombined{ + ContainerID: containerID, + ContextSys: mon.SyscallContext{ + PID: event.PID, + PPID: event.PPID, + UID: event.UID, + + HostPID: event.HostPID, + HostPPID: event.HostPPID, + }, + }, readLink) + + if ckv, ok := p.ContainerMap[containerID]; ok { + log.PolicyName = ckv.Policy + log.Type = "MatchedPolicy" + } + + log.Operation = "Process" + + if event.Retval >= 0 { + log.Result = "Passed" + } else { + log.Result = "Permission denied" + } + + log.Enforcer = base.PRESET_ENFORCER + NAME + + if len(log.Source) == 0 { + log.Source = string(bytes.Trim(event.Data.Source[:], "\x00")) + } + + // memfd:, /dev/shm/*, /run/shm/* + log.Resource = string(bytes.Trim(event.Data.Path[:], "\x00")) + + p.Logger.PushLog(log) + + } +} + +// RegisterContainer registers a container to filelessexec preset +func (p *Preset) RegisterContainer(containerID string, pidns, mntns uint32) { + ckv := NsKey{PidNS: pidns, MntNS: mntns} + + p.ContainerMapLock.Lock() + defer p.ContainerMapLock.Unlock() + p.Logger.Printf("[FilelessExec] Registered container with id: %s\n", containerID) + p.ContainerMap[containerID] = ContainerVal{NsKey: ckv} +} + +// UnregisterContainer func unregisters a container from filelessexec preset +func (p *Preset) UnregisterContainer(containerID string) { + p.ContainerMapLock.Lock() + defer p.ContainerMapLock.Unlock() + + if val, ok := p.ContainerMap[containerID]; ok { + if err := p.DeleteContainerIDFromMap(containerID, val.NsKey); err != nil { + p.Logger.Errf("error deleting container %s: %s", containerID, err.Error()) + return + } + p.Logger.Printf("[FilelessExec] Unregistered container with id: %s\n", containerID) + delete(p.ContainerMap, containerID) + } +} + +// AddContainerIDToMap adds a container id to ebpf map +func (p *Preset) AddContainerIDToMap(id string, ckv NsKey, action string) error { + p.Logger.Printf("[FilelessExec] adding container with id to anon_map exec map: %s\n", id) + a := base.Block + if action == "Audit" { + a = base.Audit + } + if err := p.BPFContainerMap.Put(ckv, a); err != nil { + p.Logger.Errf("error adding container %s to outer map: %s", id, err) + return err + } + return nil +} + +// DeleteContainerIDFromMap deletes a container id from ebpf map +func (p *Preset) DeleteContainerIDFromMap(id string, ckv NsKey) error { + p.Logger.Printf("[FilelessExec] deleting container with id to anon_map exec map: %s\n", id) + if err := p.BPFContainerMap.Delete(ckv); err != nil { + if !errors.Is(err, os.ErrNotExist) { + p.Logger.Errf("error deleting container %s in anon_map_exec_preset_containers map: %s", id, err.Error()) + return err + } + } + return nil +} + +// UpdateSecurityPolicies updates filelessexec policy for a given endpoint +func (p *Preset) UpdateSecurityPolicies(endPoint tp.EndPoint) { + var filelessExecPresetRulePresent bool + for _, cid := range endPoint.Containers { + filelessExecPresetRulePresent = false + p.Logger.Printf("Updating container preset rules for %s", cid) + for _, secPolicy := range endPoint.SecurityPolicies { + for _, preset := range secPolicy.Spec.Presets { + if preset == tp.FilelessExec { + p.Logger.Printf("container matched for fileless exec rule: %s", cid) + filelessExecPresetRulePresent = true + p.ContainerMapLock.RLock() + // Check if Container ID is registered in Map or not + ckv, ok := p.ContainerMap[cid] + p.ContainerMapLock.RUnlock() + if !ok { + // It maybe possible that CRI has unregistered the containers but K8s construct still has not sent this update while the policy was being applied, + // so the need to check if the container is present in the map before we apply policy. + p.Logger.Warnf("container not registered in map: %s", cid) + + return + } + ckv.Policy = secPolicy.Metadata["policyName"] + p.ContainerMapLock.Lock() + p.ContainerMap[cid] = ckv + err := p.AddContainerIDToMap(cid, ckv.NsKey, secPolicy.Spec.Action) + if err != nil { + p.Logger.Warnf("updating policy for container %s :%s ", cid, err) + } + p.ContainerMapLock.Unlock() + } + } + } + if !filelessExecPresetRulePresent { + p.ContainerMapLock.RLock() + ckv := p.ContainerMap[cid] + _ = p.DeleteContainerIDFromMap(cid, ckv.NsKey) + p.ContainerMapLock.RUnlock() + } + } +} + +// Destroy func gracefully destroys filelessexec preset +func (p *Preset) Destroy() error { + if p == nil { + return nil + } + var errBPFCleanUp error + + if err := p.obj.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + + if err := p.Link.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + + p.ContainerMapLock.Lock() + + if p.BPFContainerMap != nil { + if err := p.BPFContainerMap.Unpin(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + if err := p.BPFContainerMap.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + } + + p.ContainerMapLock.Unlock() + + if p.Events != nil { + if err := p.Events.Close(); err != nil { + p.Logger.Err(err.Error()) + errBPFCleanUp = errors.Join(errBPFCleanUp, err) + } + } + + p = nil + return errBPFCleanUp +} diff --git a/KubeArmor/presets/presets.go b/KubeArmor/presets/presets.go new file mode 100644 index 0000000000..fba88c0dc1 --- /dev/null +++ b/KubeArmor/presets/presets.go @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of KubeArmor + +// Package presets contains preset rules components +package presets + +import ( + "errors" + + fd "github.com/kubearmor/KubeArmor/KubeArmor/feeder" + mon "github.com/kubearmor/KubeArmor/KubeArmor/monitor" + anonmap "github.com/kubearmor/KubeArmor/KubeArmor/presets/anonmapexec" + "github.com/kubearmor/KubeArmor/KubeArmor/presets/base" + filelessexec "github.com/kubearmor/KubeArmor/KubeArmor/presets/filelessexec" + tp "github.com/kubearmor/KubeArmor/KubeArmor/types" +) + +// Preset struct +type Preset struct { + base.Preset + + List map[string]base.PresetInterface +} + +// NewPreset returns an instance of Preset +func NewPreset(logger *fd.Feeder, monitor *mon.SystemMonitor) *Preset { + p := &Preset{} + + p.List = make(map[string]base.PresetInterface) + p.Logger = logger + p.Monitor = monitor + + // add all presets + p.List[anonmap.NAME] = anonmap.NewAnonMapExecPreset() + p.List[filelessexec.NAME] = filelessexec.NewFilelessExecPreset() + + // register all presets + p.RegisterPresets() + + if len(p.List) > 0 { + return p + } + return nil +} + +// RegisterPresets initiates and adds presets to map +func (p *Preset) RegisterPresets() { + for k, v := range p.List { + _, err := v.RegisterPreset(p.Logger, p.Monitor) + if err != nil { + delete(p.List, k) + } + } +} + +// RegisterContainer registers container identifiers +func (p *Preset) RegisterContainer(containerID string, pidns, mntns uint32) { + for _, v := range p.List { + v.RegisterContainer(containerID, pidns, mntns) + } +} + +// UnregisterContainer removes container identifiers +func (p *Preset) UnregisterContainer(containerID string) { + for _, v := range p.List { + v.UnregisterContainer(containerID) + } +} + +// UpdateSecurityPolicies Function +func (p *Preset) UpdateSecurityPolicies(endPoint tp.EndPoint) { + for _, v := range p.List { + v.UpdateSecurityPolicies(endPoint) + } +} + +// Destroy Function +func (p *Preset) Destroy() error { + var destroyErr error + for _, v := range p.List { + err := v.Destroy() + if err != nil { + destroyErr = errors.Join(destroyErr, err) + } + } + return destroyErr +} diff --git a/KubeArmor/presets/protectEnv/preset.go b/KubeArmor/presets/protectEnv/preset.go new file mode 100644 index 0000000000..c85f1a217b --- /dev/null +++ b/KubeArmor/presets/protectEnv/preset.go @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of KubeArmor + +// Package protectenv contains components for protectenv preset rule +package protectenv + +import ( + "bytes" + "encoding/binary" + "errors" + "log" + "sync" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/ringbuf" + "github.com/cilium/ebpf/rlimit" + "github.com/kubearmor/KubeArmor/KubeArmor/presets/base" + + fd "github.com/kubearmor/KubeArmor/KubeArmor/feeder" + mon "github.com/kubearmor/KubeArmor/KubeArmor/monitor" + tp "github.com/kubearmor/KubeArmor/KubeArmor/types" +) + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang protectenv ../../BPF/protectenv.bpf.c -type pevent -no-global-types -- -I/usr/include/ -O2 -g + +type eventBPF struct { + Pid uint32 + PidNS uint32 + MntNS uint32 + Comm [80]uint8 +} + +// EnvPreset struct +type EnvPreset struct { + base.Preset + + BPFContainerMap *ebpf.Map + + // events + Events *ringbuf.Reader + EventsChannel chan []byte + + // ContainerID -> NsKey + rules + ContainerMap map[string]interface{} + ContainerMapLock *sync.RWMutex + + Link link.Link + + obj protectenvObjects +} + +// RegisterPreset register protectenv preset and returns an instance of EnvPreset on success +// otherwise returns an error +func RegisterPreset(logger *fd.Feeder, monitor *mon.SystemMonitor) (*EnvPreset, error) { + p := &EnvPreset{} + + p.Logger = logger + p.Monitor = monitor + var err error + + if err = rlimit.RemoveMemlock(); err != nil { + p.Logger.Errf("Error removing rlimit %v", err) + return nil, nil // Doesn't require clean up so not returning err + } + + p.BPFContainerMap, _ = ebpf.NewMapWithOptions(&ebpf.MapSpec{ + Type: ebpf.Hash, + KeySize: 8, + ValueSize: 4, + MaxEntries: 256, + Pinning: ebpf.PinByName, + Name: "kubearmor_preset_containers", + }, ebpf.MapOptions{}) + + if err := loadProtectenvObjects(&p.obj, &ebpf.CollectionOptions{ + Maps: ebpf.MapOptions{}, + }); err != nil { + p.Logger.Errf("error loading BPF LSM objects: %v", err) + return p, err + } + + p.Link, err = link.AttachLSM(link.LSMOptions{Program: p.obj.EnforceFile}) + if err != nil { + p.Logger.Errf("opening lsm %s: %s", p.obj.EnforceFile.String(), err) + return p, err + } + + p.Events, err = ringbuf.NewReader(p.obj.Events) + if err != nil { + p.Logger.Errf("opening ringbuf reader: %s", err) + return p, err + } + p.EventsChannel = make(chan []byte, mon.SyscallChannelSize) + + go p.TraceEvents() + + return p, nil + +} + +// TraceEvents traces events generated by bpflsm enforcer +func (p *EnvPreset) TraceEvents() { + + if p.Events == nil { + p.Logger.Err("ringbuf reader is nil, exiting trace events") + } + p.Logger.Print("Starting TraceEvents from Env Presets") + go func() { + for { + + record, err := p.Events.Read() + if err != nil { + if errors.Is(err, ringbuf.ErrClosed) { + // This should only happen when we call DestroyMonitor while terminating the process. + // Adding a Warn just in case it happens at runtime, to help debug + p.Logger.Warnf("Ring Buffer closed, exiting TraceEvents %s", err.Error()) + return + } + p.Logger.Warnf("Ringbuf error reading %s", err.Error()) + continue + } + + p.EventsChannel <- record.RawSample + + } + }() + + for { + + dataRaw := <-p.EventsChannel + + var event eventBPF + + if err := binary.Read(bytes.NewBuffer(dataRaw), binary.LittleEndian, &event); err != nil { + log.Printf("parsing ringbuf event: %s", err) + continue + } + + containerID := "" + + if event.PidNS != 0 && event.MntNS != 0 { + containerID = p.Monitor.LookupContainerID(event.PidNS, event.MntNS) + } + + if containerID != "" { + p.Logger.Printf("Alert event from cid %s for protect env preset with deets %+v", containerID, event) + } + + } +} + +// RegisterContainer registers a container +func (p *EnvPreset) RegisterContainer(containerID string, pidns, mntns uint32) { + +} + +// UnregisterContainer unregisters a container +func (p *EnvPreset) UnregisterContainer(containerID string) { + +} + +// UpdateSecurityPolicies updates protectenv policy rules +func (p *EnvPreset) UpdateSecurityPolicies(endPoint tp.EndPoint) { + +} + +// Destroy func deletes EnvPreset instance +func (p *EnvPreset) Destroy() error { + return nil +} diff --git a/KubeArmor/presets/protectEnv/protectenv_bpfeb.go b/KubeArmor/presets/protectEnv/protectenv_bpfeb.go new file mode 100644 index 0000000000..b12bbfa452 --- /dev/null +++ b/KubeArmor/presets/protectEnv/protectenv_bpfeb.go @@ -0,0 +1,150 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64 + +package protectenv + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type protectenvBufsK struct { + Path [256]int8 + Source [256]int8 +} + +type protectenvBufsT struct{ Buf [32768]int8 } + +// loadProtectenv returns the embedded CollectionSpec for protectenv. +func loadProtectenv() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_ProtectenvBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load protectenv: %w", err) + } + + return spec, err +} + +// loadProtectenvObjects loads protectenv and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *protectenvObjects +// *protectenvPrograms +// *protectenvMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadProtectenvObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadProtectenv() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// protectenvSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type protectenvSpecs struct { + protectenvProgramSpecs + protectenvMapSpecs +} + +// protectenvSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type protectenvProgramSpecs struct { + EnforceFile *ebpf.ProgramSpec `ebpf:"enforce_file"` +} + +// protectenvMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type protectenvMapSpecs struct { + Bufk *ebpf.MapSpec `ebpf:"bufk"` + Bufs *ebpf.MapSpec `ebpf:"bufs"` + BufsOff *ebpf.MapSpec `ebpf:"bufs_off"` + Events *ebpf.MapSpec `ebpf:"events"` + KubearmorAlertThrottle *ebpf.MapSpec `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.MapSpec `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.MapSpec `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.MapSpec `ebpf:"kubearmor_events"` + ProtectenvPresetContainers *ebpf.MapSpec `ebpf:"protectenv_preset_containers"` +} + +// protectenvObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadProtectenvObjects or ebpf.CollectionSpec.LoadAndAssign. +type protectenvObjects struct { + protectenvPrograms + protectenvMaps +} + +func (o *protectenvObjects) Close() error { + return _ProtectenvClose( + &o.protectenvPrograms, + &o.protectenvMaps, + ) +} + +// protectenvMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadProtectenvObjects or ebpf.CollectionSpec.LoadAndAssign. +type protectenvMaps struct { + Bufk *ebpf.Map `ebpf:"bufk"` + Bufs *ebpf.Map `ebpf:"bufs"` + BufsOff *ebpf.Map `ebpf:"bufs_off"` + Events *ebpf.Map `ebpf:"events"` + KubearmorAlertThrottle *ebpf.Map `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.Map `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.Map `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.Map `ebpf:"kubearmor_events"` + ProtectenvPresetContainers *ebpf.Map `ebpf:"protectenv_preset_containers"` +} + +func (m *protectenvMaps) Close() error { + return _ProtectenvClose( + m.Bufk, + m.Bufs, + m.BufsOff, + m.Events, + m.KubearmorAlertThrottle, + m.KubearmorConfig, + m.KubearmorContainers, + m.KubearmorEvents, + m.ProtectenvPresetContainers, + ) +} + +// protectenvPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadProtectenvObjects or ebpf.CollectionSpec.LoadAndAssign. +type protectenvPrograms struct { + EnforceFile *ebpf.Program `ebpf:"enforce_file"` +} + +func (p *protectenvPrograms) Close() error { + return _ProtectenvClose( + p.EnforceFile, + ) +} + +func _ProtectenvClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed protectenv_bpfeb.o +var _ProtectenvBytes []byte diff --git a/KubeArmor/presets/protectEnv/protectenv_bpfeb.o b/KubeArmor/presets/protectEnv/protectenv_bpfeb.o new file mode 100644 index 0000000000..73c376dd95 Binary files /dev/null and b/KubeArmor/presets/protectEnv/protectenv_bpfeb.o differ diff --git a/KubeArmor/presets/protectEnv/protectenv_bpfel.go b/KubeArmor/presets/protectEnv/protectenv_bpfel.go new file mode 100644 index 0000000000..61249f9b7a --- /dev/null +++ b/KubeArmor/presets/protectEnv/protectenv_bpfel.go @@ -0,0 +1,150 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 + +package protectenv + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type protectenvBufsK struct { + Path [256]int8 + Source [256]int8 +} + +type protectenvBufsT struct{ Buf [32768]int8 } + +// loadProtectenv returns the embedded CollectionSpec for protectenv. +func loadProtectenv() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_ProtectenvBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load protectenv: %w", err) + } + + return spec, err +} + +// loadProtectenvObjects loads protectenv and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *protectenvObjects +// *protectenvPrograms +// *protectenvMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadProtectenvObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadProtectenv() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// protectenvSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type protectenvSpecs struct { + protectenvProgramSpecs + protectenvMapSpecs +} + +// protectenvSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type protectenvProgramSpecs struct { + EnforceFile *ebpf.ProgramSpec `ebpf:"enforce_file"` +} + +// protectenvMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type protectenvMapSpecs struct { + Bufk *ebpf.MapSpec `ebpf:"bufk"` + Bufs *ebpf.MapSpec `ebpf:"bufs"` + BufsOff *ebpf.MapSpec `ebpf:"bufs_off"` + Events *ebpf.MapSpec `ebpf:"events"` + KubearmorAlertThrottle *ebpf.MapSpec `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.MapSpec `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.MapSpec `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.MapSpec `ebpf:"kubearmor_events"` + ProtectenvPresetContainers *ebpf.MapSpec `ebpf:"protectenv_preset_containers"` +} + +// protectenvObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadProtectenvObjects or ebpf.CollectionSpec.LoadAndAssign. +type protectenvObjects struct { + protectenvPrograms + protectenvMaps +} + +func (o *protectenvObjects) Close() error { + return _ProtectenvClose( + &o.protectenvPrograms, + &o.protectenvMaps, + ) +} + +// protectenvMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadProtectenvObjects or ebpf.CollectionSpec.LoadAndAssign. +type protectenvMaps struct { + Bufk *ebpf.Map `ebpf:"bufk"` + Bufs *ebpf.Map `ebpf:"bufs"` + BufsOff *ebpf.Map `ebpf:"bufs_off"` + Events *ebpf.Map `ebpf:"events"` + KubearmorAlertThrottle *ebpf.Map `ebpf:"kubearmor_alert_throttle"` + KubearmorConfig *ebpf.Map `ebpf:"kubearmor_config"` + KubearmorContainers *ebpf.Map `ebpf:"kubearmor_containers"` + KubearmorEvents *ebpf.Map `ebpf:"kubearmor_events"` + ProtectenvPresetContainers *ebpf.Map `ebpf:"protectenv_preset_containers"` +} + +func (m *protectenvMaps) Close() error { + return _ProtectenvClose( + m.Bufk, + m.Bufs, + m.BufsOff, + m.Events, + m.KubearmorAlertThrottle, + m.KubearmorConfig, + m.KubearmorContainers, + m.KubearmorEvents, + m.ProtectenvPresetContainers, + ) +} + +// protectenvPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadProtectenvObjects or ebpf.CollectionSpec.LoadAndAssign. +type protectenvPrograms struct { + EnforceFile *ebpf.Program `ebpf:"enforce_file"` +} + +func (p *protectenvPrograms) Close() error { + return _ProtectenvClose( + p.EnforceFile, + ) +} + +func _ProtectenvClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed protectenv_bpfel.o +var _ProtectenvBytes []byte diff --git a/KubeArmor/presets/protectEnv/protectenv_bpfel.o b/KubeArmor/presets/protectEnv/protectenv_bpfel.o new file mode 100644 index 0000000000..8749045318 Binary files /dev/null and b/KubeArmor/presets/protectEnv/protectenv_bpfel.o differ diff --git a/KubeArmor/types/types.go b/KubeArmor/types/types.go index 1b6046f18d..ba27b16a05 100644 --- a/KubeArmor/types/types.go +++ b/KubeArmor/types/types.go @@ -526,6 +526,14 @@ type SyscallsType struct { Message string `json:"message,omitempty"` } +// PresetType type +type PresetType string + +const ( + AnonMapExec PresetType = "anonymousMapExec" + FilelessExec PresetType = "filelessExec" +) + // SecuritySpec Structure type SecuritySpec struct { Selector SelectorType `json:"selector"` @@ -535,6 +543,7 @@ type SecuritySpec struct { Network NetworkType `json:"network,omitempty"` Capabilities CapabilitiesType `json:"capabilities,omitempty"` Syscalls SyscallsType `json:"syscalls,omitempty"` + Presets []PresetType `json:"presets,omitempty"` AppArmor string `json:"apparmor,omitempty"` diff --git a/deployments/CRD/KubeArmorClusterPolicy.yaml b/deployments/CRD/KubeArmorClusterPolicy.yaml index 75bfcced26..3d7acc39fd 100644 --- a/deployments/CRD/KubeArmorClusterPolicy.yaml +++ b/deployments/CRD/KubeArmorClusterPolicy.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorclusterpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1188,9 +1191,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/deployments/CRD/KubeArmorHostPolicy.yaml b/deployments/CRD/KubeArmorHostPolicy.yaml index 497c216ff7..811ea5fa54 100644 --- a/deployments/CRD/KubeArmorHostPolicy.yaml +++ b/deployments/CRD/KubeArmorHostPolicy.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorhostpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1177,9 +1180,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/deployments/CRD/KubeArmorPolicy.yaml b/deployments/CRD/KubeArmorPolicy.yaml index ce3ef593fa..dd9cc6ed91 100644 --- a/deployments/CRD/KubeArmorPolicy.yaml +++ b/deployments/CRD/KubeArmorPolicy.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -36,14 +34,19 @@ spec: description: KubeArmorPolicy is the Schema for the kubearmorpolicies API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -292,6 +295,10 @@ spec: type: string type: array type: object + presets: + items: + type: string + type: array process: properties: action: @@ -1172,9 +1179,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/deployments/helm/KubeArmor/templates/crds/csp.yaml b/deployments/helm/KubeArmor/templates/crds/csp.yaml index 75bfcced26..3d7acc39fd 100644 --- a/deployments/helm/KubeArmor/templates/crds/csp.yaml +++ b/deployments/helm/KubeArmor/templates/crds/csp.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorclusterpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1188,9 +1191,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/deployments/helm/KubeArmor/templates/crds/hsp.yaml b/deployments/helm/KubeArmor/templates/crds/hsp.yaml index 497c216ff7..811ea5fa54 100644 --- a/deployments/helm/KubeArmor/templates/crds/hsp.yaml +++ b/deployments/helm/KubeArmor/templates/crds/hsp.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorhostpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1177,9 +1180,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/deployments/helm/KubeArmor/templates/crds/ksp.yaml b/deployments/helm/KubeArmor/templates/crds/ksp.yaml index ce3ef593fa..dd9cc6ed91 100644 --- a/deployments/helm/KubeArmor/templates/crds/ksp.yaml +++ b/deployments/helm/KubeArmor/templates/crds/ksp.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -36,14 +34,19 @@ spec: description: KubeArmorPolicy is the Schema for the kubearmorpolicies API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -292,6 +295,10 @@ spec: type: string type: array type: object + presets: + items: + type: string + type: array process: properties: action: @@ -1172,9 +1179,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/pkg/KubeArmorController/Makefile b/pkg/KubeArmorController/Makefile index e230f0446b..5c10882f1f 100644 --- a/pkg/KubeArmorController/Makefile +++ b/pkg/KubeArmorController/Makefile @@ -8,7 +8,7 @@ IMG ?= kubearmor/kubearmor-controller # Image Tag to use all building/pushing image targets TAG ?= v0.1 # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) -CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" +CRD_OPTIONS ?= "crd" # Target platforms for build PLATFORM ?= "linux/amd64,linux/arm64/v8" diff --git a/pkg/KubeArmorController/api/security.kubearmor.com/v1/common.go b/pkg/KubeArmorController/api/security.kubearmor.com/v1/common.go index 4d9611c612..62cba7e546 100644 --- a/pkg/KubeArmorController/api/security.kubearmor.com/v1/common.go +++ b/pkg/KubeArmorController/api/security.kubearmor.com/v1/common.go @@ -332,3 +332,9 @@ type SyscallsType struct { // +kubebuilder:validation:optional Message string `json:"message,omitempty"` } + +type PresetType string + +const ( + FilelessProcessExec PresetType = "filelessProcessExec" +) diff --git a/pkg/KubeArmorController/api/security.kubearmor.com/v1/kubearmorpolicy_types.go b/pkg/KubeArmorController/api/security.kubearmor.com/v1/kubearmorpolicy_types.go index d9c1fb1526..5f54665b19 100644 --- a/pkg/KubeArmorController/api/security.kubearmor.com/v1/kubearmorpolicy_types.go +++ b/pkg/KubeArmorController/api/security.kubearmor.com/v1/kubearmorpolicy_types.go @@ -51,6 +51,7 @@ type KubeArmorPolicySpec struct { Network NetworkType `json:"network,omitempty"` Capabilities CapabilitiesType `json:"capabilities,omitempty"` Syscalls SyscallsType `json:"syscalls,omitempty"` + Presets []PresetType `json:"presets,omitempty"` AppArmor string `json:"apparmor,omitempty"` diff --git a/pkg/KubeArmorController/api/security.kubearmor.com/v1/zz_generated.deepcopy.go b/pkg/KubeArmorController/api/security.kubearmor.com/v1/zz_generated.deepcopy.go index 414ab4828b..9e8f336820 100644 --- a/pkg/KubeArmorController/api/security.kubearmor.com/v1/zz_generated.deepcopy.go +++ b/pkg/KubeArmorController/api/security.kubearmor.com/v1/zz_generated.deepcopy.go @@ -471,6 +471,11 @@ func (in *KubeArmorPolicySpec) DeepCopyInto(out *KubeArmorPolicySpec) { in.Network.DeepCopyInto(&out.Network) in.Capabilities.DeepCopyInto(&out.Capabilities) in.Syscalls.DeepCopyInto(&out.Syscalls) + if in.Presets != nil { + in, out := &in.Presets, &out.Presets + *out = make([]PresetType, len(*in)) + copy(*out, *in) + } if in.Tags != nil { in, out := &in.Tags, &out.Tags *out = make([]string, len(*in)) diff --git a/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorclusterpolicies.yaml b/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorclusterpolicies.yaml index 75bfcced26..3d7acc39fd 100644 --- a/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorclusterpolicies.yaml +++ b/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorclusterpolicies.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorclusterpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1188,9 +1191,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorhostpolicies.yaml b/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorhostpolicies.yaml index 497c216ff7..811ea5fa54 100644 --- a/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorhostpolicies.yaml +++ b/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorhostpolicies.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorhostpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1177,9 +1180,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorpolicies.yaml b/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorpolicies.yaml index ce3ef593fa..dd9cc6ed91 100644 --- a/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorpolicies.yaml +++ b/pkg/KubeArmorController/config/crd/bases/security.kubearmor.com_kubearmorpolicies.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -36,14 +34,19 @@ spec: description: KubeArmorPolicy is the Schema for the kubearmorpolicies API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -292,6 +295,10 @@ spec: type: string type: array type: object + presets: + items: + type: string + type: array process: properties: action: @@ -1172,9 +1179,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/pkg/KubeArmorController/config/rbac/role.yaml b/pkg/KubeArmorController/config/rbac/role.yaml index 73578989bf..d79e4c7546 100644 --- a/pkg/KubeArmorController/config/rbac/role.yaml +++ b/pkg/KubeArmorController/config/rbac/role.yaml @@ -1,7 +1,7 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: manager-role rules: - apiGroups: [""] diff --git a/pkg/KubeArmorController/config/webhook/manifests.yaml b/pkg/KubeArmorController/config/webhook/manifests.yaml index d152733068..537d93cd04 100644 --- a/pkg/KubeArmorController/config/webhook/manifests.yaml +++ b/pkg/KubeArmorController/config/webhook/manifests.yaml @@ -1,9 +1,7 @@ - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: - creationTimestamp: null name: mutating-webhook-configuration webhooks: - admissionReviewVersions: diff --git a/pkg/KubeArmorController/crd/KubeArmorClusterPolicy.yaml b/pkg/KubeArmorController/crd/KubeArmorClusterPolicy.yaml index 75bfcced26..3d7acc39fd 100644 --- a/pkg/KubeArmorController/crd/KubeArmorClusterPolicy.yaml +++ b/pkg/KubeArmorController/crd/KubeArmorClusterPolicy.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorclusterpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1188,9 +1191,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/pkg/KubeArmorController/crd/KubeArmorHostPolicy.yaml b/pkg/KubeArmorController/crd/KubeArmorHostPolicy.yaml index 497c216ff7..811ea5fa54 100644 --- a/pkg/KubeArmorController/crd/KubeArmorHostPolicy.yaml +++ b/pkg/KubeArmorController/crd/KubeArmorHostPolicy.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorhostpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -37,14 +35,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1177,9 +1180,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/pkg/KubeArmorController/crd/KubeArmorPolicy.yaml b/pkg/KubeArmorController/crd/KubeArmorPolicy.yaml index ce3ef593fa..dd9cc6ed91 100644 --- a/pkg/KubeArmorController/crd/KubeArmorPolicy.yaml +++ b/pkg/KubeArmorController/crd/KubeArmorPolicy.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.14.0 name: kubearmorpolicies.security.kubearmor.com spec: group: security.kubearmor.com @@ -36,14 +34,19 @@ spec: description: KubeArmorPolicy is the Schema for the kubearmorpolicies API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -292,6 +295,10 @@ spec: type: string type: array type: object + presets: + items: + type: string + type: array process: properties: action: @@ -1172,9 +1179,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/tests/k8s_env/multicontainer/multicontainer_test.go b/tests/k8s_env/multicontainer/multicontainer_test.go index 6ab4445a4c..219eed8640 100644 --- a/tests/k8s_env/multicontainer/multicontainer_test.go +++ b/tests/k8s_env/multicontainer/multicontainer_test.go @@ -7,6 +7,8 @@ import ( "fmt" "time" + "github.com/kubearmor/KubeArmor/protobuf" + . "github.com/kubearmor/KubeArmor/tests/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -59,13 +61,16 @@ var _ = Describe("Multicontainer", func() { fmt.Printf("---START---\n%s---END---\n", sout) Expect(sout).To(MatchRegexp(".*Permission denied")) + expect := protobuf.Alert{ + PolicyName: "container-1-block-ls", + Severity: "2", + ContainerName: "container-1", + } + // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("container-1-block-ls")) - Expect(alerts[0].Severity).To(Equal("2")) - Expect(alerts[0].ContainerName).To(Equal("container-1")) + Expect(res.Found).To(BeTrue()) //container-2 should run ls sout, _, err = K8sExecInPodWithContainer(multicontainer, "multicontainer", "container-2", []string{"bash", "-c", "ls"}) @@ -87,12 +92,16 @@ var _ = Describe("Multicontainer", func() { Expect(sout).To(MatchRegexp(".*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "empty-array-ls-block", + Severity: "4", + ContainerName: "container-1", + } + + // check policy violation alert + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("empty-array-ls-block")) - Expect(alerts[0].Severity).To(Equal("4")) - Expect(alerts[0].ContainerName).To(Equal("container-1")) + Expect(res.Found).To(BeTrue()) sout, _, err = K8sExecInPodWithContainer(multicontainer, "multicontainer", "container-2", []string{"bash", "-c", "ls"}) Expect(err).To(BeNil()) @@ -100,13 +109,16 @@ var _ = Describe("Multicontainer", func() { Expect(sout).To(MatchRegexp(".*Permission denied")) // check policy violation alert - _, alerts, err = KarmorGetLogs(5*time.Second, 1) - Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("empty-array-ls-block")) - Expect(alerts[0].Severity).To(Equal("4")) - Expect(alerts[0].ContainerName).To(Equal("container-2")) + expect = protobuf.Alert{ + PolicyName: "empty-array-ls-block", + Severity: "4", + ContainerName: "container-2", + } + // check policy violation alert + res, err = KarmorGetTargetAlert(5*time.Second, &expect) + Expect(err).To(BeNil()) + Expect(res.Found).To(BeTrue()) }) //kubearmor.io/container.name: "" @@ -123,12 +135,16 @@ var _ = Describe("Multicontainer", func() { Expect(sout).To(MatchRegexp(".*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "empty-array-ls-block", + Severity: "4", + ContainerName: "container-1", + } + + // check policy violation alert + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("empty-array-ls-block")) - Expect(alerts[0].Severity).To(Equal("4")) - Expect(alerts[0].ContainerName).To(Equal("container-1")) + Expect(res.Found).To(BeTrue()) sout, _, err = K8sExecInPodWithContainer(multicontainer, "multicontainer", "container-2", []string{"bash", "-c", "ls"}) Expect(err).To(BeNil()) @@ -136,12 +152,16 @@ var _ = Describe("Multicontainer", func() { Expect(sout).To(MatchRegexp(".*Permission denied")) // check policy violation alert - _, alerts, err = KarmorGetLogs(5*time.Second, 1) + expect = protobuf.Alert{ + PolicyName: "empty-array-ls-block", + Severity: "4", + ContainerName: "container-2", + } + + // check policy violation alert + res, err = KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("empty-array-ls-block")) - Expect(alerts[0].Severity).To(Equal("4")) - Expect(alerts[0].ContainerName).To(Equal("container-2")) + Expect(res.Found).To(BeTrue()) }) @@ -158,12 +178,16 @@ var _ = Describe("Multicontainer", func() { Expect(sout).To(MatchRegexp(".*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "malformated-array-ls-block", + Severity: "4", + ContainerName: "container-1", + } + + // check policy violation alert + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("malformated-array-ls-block")) - Expect(alerts[0].Severity).To(Equal("4")) - Expect(alerts[0].ContainerName).To(Equal("container-1")) + Expect(res.Found).To(BeTrue()) //container-2 should run ls sout, _, err = K8sExecInPodWithContainer(multicontainer, "multicontainer", "container-2", []string{"bash", "-c", "ls"}) diff --git a/tests/k8s_env/presets/presets_suite_test.go b/tests/k8s_env/presets/presets_suite_test.go new file mode 100644 index 0000000000..2910a1e623 --- /dev/null +++ b/tests/k8s_env/presets/presets_suite_test.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Authors of KubeArmor + +package presets_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestPresets(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Presets Suite") +} diff --git a/tests/k8s_env/presets/presets_test.go b/tests/k8s_env/presets/presets_test.go new file mode 100644 index 0000000000..93103f335e --- /dev/null +++ b/tests/k8s_env/presets/presets_test.go @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Authors of KubeArmor + +package presets + +import ( + "fmt" + "strings" + "time" + + "github.com/kubearmor/KubeArmor/protobuf" + . "github.com/kubearmor/KubeArmor/tests/util" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = BeforeSuite(func() { + // install wordpress-mysql app + err := K8sApply([]string{"res/python-deployment.yaml"}) + Expect(err).To(BeNil()) + + // delete all KSPs + err = DeleteAllKsp() + Expect(err).To(BeNil()) +}) + +var _ = AfterSuite(func() { + // Delete wordpress-mysql app + err := K8sDelete([]string{"res/python-deployment.yaml"}) + Expect(err).To(BeNil()) +}) + +func getfilelessPod(name string, ant []string) string { + pods, err := K8sGetPods(name, "presets", ant, 60) + Expect(err).To(BeNil()) + Expect(len(pods)).To(Equal(1)) + return pods[0] +} + +var _ = Describe("Presets", func() { + var fp string + + BeforeEach(func() { + fp = getfilelessPod("fileless-", []string{"kubearmor-policy: enabled"}) + }) + + AfterEach(func() { + KarmorLogStop() + err := DeleteAllKsp() + Expect(err).To(BeNil()) + // wait for policy deletion + time.Sleep(5 * time.Second) + }) + + Describe("Policy Apply", func() { + It("can audit fileless execution", func() { + if !strings.Contains(K8sRuntimeEnforcer(), "bpf") { + Skip("fileless execution preset requires bpf-lsm") + } + // Apply policy + err := K8sApplyFile("res/ksp-preset-audit-fileless.yaml") + Expect(err).To(BeNil()) + + // Start Kubearmor Logs + err = KarmorLogStart("policy", "presets", "Process", fp) + Expect(err).To(BeNil()) + + // wait for policy creation + time.Sleep(5 * time.Second) + + sout, _, err := K8sExecInPod(fp, "presets", []string{"sh", "-c", "python3 ls.py"}) + Expect(err).To(BeNil()) + fmt.Printf("---START---\n%s---END---\n", sout) + Expect(sout).To(Not(ContainSubstring("Permission denied"))) + + // check policy violation alert + expect := protobuf.Alert{ + PolicyName: "ksp-preset-audit-fileless", + Result: "Passed", + // Severity: "8", + } + + // check policy violation alert + res, err := KarmorGetTargetAlert(5*time.Second, &expect) + Expect(err).To(BeNil()) + Expect(res.Found).To(BeTrue()) + }) + + It("can block fileless execution", func() { + if !strings.Contains(K8sRuntimeEnforcer(), "bpf") { + Skip("fileless execution preset requires bpf-lsm") + } + // Apply policy + err := K8sApplyFile("res/ksp-preset-block-fileless.yaml") + Expect(err).To(BeNil()) + + // Start Kubearmor Logs + err = KarmorLogStart("policy", "presets", "Process", fp) + Expect(err).To(BeNil()) + + // wait for policy creation + time.Sleep(5 * time.Second) + + sout, _, err := K8sExecInPod(fp, "presets", []string{"sh", "-c", "python3 ls.py"}) + Expect(err).To(BeNil()) + fmt.Printf("---START---\n%s---END---\n", sout) + Expect(sout).To(MatchRegexp(".*Permission denied")) + + // check policy violation alert + expect := protobuf.Alert{ + PolicyName: "ksp-preset-block-fileless", + Result: "Permission denied", + // Severity: "8", + } + + // check policy violation alert + res, err := KarmorGetTargetAlert(5*time.Second, &expect) + Expect(err).To(BeNil()) + Expect(res.Found).To(BeTrue()) + }) + }) +}) diff --git a/tests/k8s_env/presets/res/ksp-preset-audit-fileless.yaml b/tests/k8s_env/presets/res/ksp-preset-audit-fileless.yaml new file mode 100644 index 0000000000..ca0a4b0280 --- /dev/null +++ b/tests/k8s_env/presets/res/ksp-preset-audit-fileless.yaml @@ -0,0 +1,13 @@ +apiVersion: security.kubearmor.com/v1 +kind: KubeArmorPolicy +metadata: + name: ksp-preset-audit-fileless + namespace: presets +spec: + action: Audit + presets: + - filelessExec + selector: + matchLabels: + app: fileless + severity: 8 \ No newline at end of file diff --git a/tests/k8s_env/presets/res/ksp-preset-block-fileless.yaml b/tests/k8s_env/presets/res/ksp-preset-block-fileless.yaml new file mode 100644 index 0000000000..503aa0c21e --- /dev/null +++ b/tests/k8s_env/presets/res/ksp-preset-block-fileless.yaml @@ -0,0 +1,13 @@ +apiVersion: security.kubearmor.com/v1 +kind: KubeArmorPolicy +metadata: + name: ksp-preset-block-fileless + namespace: presets +spec: + action: Block + presets: + - filelessExec + selector: + matchLabels: + app: fileless + severity: 8 \ No newline at end of file diff --git a/tests/k8s_env/presets/res/python-deployment.yaml b/tests/k8s_env/presets/res/python-deployment.yaml new file mode 100644 index 0000000000..457e151851 --- /dev/null +++ b/tests/k8s_env/presets/res/python-deployment.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: presets +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fileless + namespace: presets + labels: + app: fileless +spec: + replicas: 1 + selector: + matchLabels: + app: fileless + template: + metadata: + labels: + app: fileless + # annotations: + # kubearmor-policy: enabled + # container.apparmor.security.beta.kubernetes.io/wordpress: localhost/kubearmor-wordpress-mysql-wordpress + spec: + containers: + - name: fileless + image: kubearmor/ubuntu-w-utils:0.2 + diff --git a/tests/k8s_env/smoke/smoke_test.go b/tests/k8s_env/smoke/smoke_test.go index f3aee3f410..f0f3b1a303 100644 --- a/tests/k8s_env/smoke/smoke_test.go +++ b/tests/k8s_env/smoke/smoke_test.go @@ -7,7 +7,9 @@ import ( "fmt" "time" + "github.com/kubearmor/KubeArmor/protobuf" "github.com/kubearmor/KubeArmor/tests/util" + . "github.com/kubearmor/KubeArmor/tests/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -72,11 +74,15 @@ var _ = Describe("Smoke", func() { Expect(sout).To(MatchRegexp("apt.*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "ksp-wordpress-block-process", + Severity: "3", + } + + // check policy violation alert + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("ksp-wordpress-block-process")) - Expect(alerts[0].Severity).To(Equal("3")) + Expect(res.Found).To(BeTrue()) }) It("can block execution of access to sensitive file with abs path", func() { @@ -98,13 +104,15 @@ var _ = Describe("Smoke", func() { Expect(sout).To(MatchRegexp("wp-config.php.*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "ksp-wordpress-block-config", + Severity: "10", + Message: "blocked access to wordpress configuration file", + } + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - fmt.Printf("%+v\n", alerts[0]) - Expect(alerts[0].PolicyName).To(Equal("ksp-wordpress-block-config")) - Expect(alerts[0].Severity).To(Equal("10")) - Expect(alerts[0].Message).To(Equal("blocked access to wordpress configuration file")) + Expect(res.Found).To(BeTrue()) + }) It("can block execution of access to sensitive file with rel path", func() { @@ -126,13 +134,14 @@ var _ = Describe("Smoke", func() { Expect(sout).To(MatchRegexp("wp-config.php.*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) - Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - fmt.Printf("%+v\n", alerts[0]) - Expect(alerts[0].PolicyName).To(Equal("ksp-wordpress-block-config")) - Expect(alerts[0].Severity).To(Equal("10")) - Expect(alerts[0].Message).To(Equal("blocked access to wordpress configuration file")) + expect := protobuf.Alert{ + PolicyName: "ksp-wordpress-block-config", + Severity: "10", + Message: "blocked access to wordpress configuration file", + } + res, err := KarmorGetTargetAlert(5*time.Second, &expect) + Expect(err).To(BeNil()) + Expect(res.Found).To(BeTrue()) }) It("can block execution of access to service account token", func() { @@ -154,11 +163,13 @@ var _ = Describe("Smoke", func() { Expect(sout).To(MatchRegexp("token.*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "ksp-wordpress-block-sa", + Severity: "7", + } + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("ksp-wordpress-block-sa")) - Expect(alerts[0].Severity).To(Equal("7")) + Expect(res.Found).To(BeTrue()) }) It("allow access for service account token to only cat", func() { @@ -181,12 +192,14 @@ var _ = Describe("Smoke", func() { Expect(sout).To(MatchRegexp("token.*Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "ksp-wordpress-lenient-allow-sa", + Severity: "7", + Source: "head", + } + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - fmt.Printf("---Alert---\n%s", alerts[0].String()) - Expect(alerts[0].PolicyName).To(Equal("ksp-wordpress-lenient-allow-sa")) - Expect(alerts[0].Severity).To(Equal("7")) + Expect(res.Found).To(BeTrue()) // trigger normal operations permitted by policy sout, _, err = K8sExecInPod(wp, "wordpress-mysql", @@ -205,9 +218,14 @@ var _ = Describe("Smoke", func() { Expect(sout).To(Not(ContainSubstring("Permission denied"))) // check for no policy violation alert - _, alerts, err = KarmorGetLogs(3*time.Second, 1) - Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically("==", 0)) + expect = protobuf.Alert{ + PolicyName: "ksp-wordpress-lenient-allow-sa", + Severity: "7", + Source: "cat", + } + res, err = KarmorGetTargetAlert(5*time.Second, &expect) + Expect(err).To(BeNil()) + Expect(res.Found).To(BeFalse()) }) It("can audit access to sensitive data path", func() { @@ -229,12 +247,13 @@ var _ = Describe("Smoke", func() { fmt.Printf("OUTPUT: %s\n", sout) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "ksp-mysql-audit-dir", + Severity: "5", + } + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - fmt.Printf("---Alert---\n%s", alerts[0].String()) - Expect(alerts[0].PolicyName).To(Equal("ksp-mysql-audit-dir")) - Expect(alerts[0].Severity).To(Equal("5")) + Expect(res.Found).To(BeTrue()) _, _, err = K8sExecInPod(sql, "wordpress-mysql", []string{"bash", "-c", fmt.Sprintf("rm %s", fname)}) @@ -283,10 +302,13 @@ var _ = Describe("Smoke", func() { Expect(sout).To(ContainSubstring("Permission denied")) // check policy violation alert - _, alerts, err := KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "ksp-wordpress-block-mount-file", + Severity: "5", + } + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(alerts[0].PolicyName).To(Equal("ksp-wordpress-block-mount-file")) - Expect(alerts[0].Severity).To(Equal("5")) + Expect(res.Found).To(BeTrue()) }) It("will allow use of tcp network protocol by curl and bash", func() { err := util.AnnotateNS("wordpress-mysql", "kubearmor-network-posture", "audit") @@ -323,11 +345,13 @@ var _ = Describe("Smoke", func() { Expect(sout).To(ContainSubstring("http://www.google.com/")) // check alert - _, alerts, err = KarmorGetLogs(5*time.Second, 1) + expect := protobuf.Alert{ + PolicyName: "DefaultPosture", + Result: "Passed", + } + res, err := KarmorGetTargetAlert(5*time.Second, &expect) Expect(err).To(BeNil()) - Expect(len(alerts)).To(BeNumerically(">=", 1)) - Expect(alerts[0].PolicyName).To(Equal("DefaultPosture")) - Expect(alerts[0].Result).To(Equal("Passed")) + Expect(res.Found).To(BeTrue()) }) }) }) diff --git a/tests/util/karmorlog.go b/tests/util/karmorlog.go index 94e2839b41..dd5be3a0a0 100644 --- a/tests/util/karmorlog.go +++ b/tests/util/karmorlog.go @@ -124,6 +124,11 @@ func getAlertWithInfo(alert *pb.Alert, target *pb.Alert) bool { return false } } + if target.Source != "" { + if !strings.Contains(alert.Source, target.Source) { + return false + } + } if target.NamespaceName != "" { if alert.NamespaceName != target.NamespaceName { return false @@ -134,6 +139,11 @@ func getAlertWithInfo(alert *pb.Alert, target *pb.Alert) bool { return false } } + if target.ContainerName != "" { + if !strings.Contains(alert.ContainerName, target.ContainerName) { + return false + } + } return true }