Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/rules improvements #338

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions pkg/containerwatcher/v1/container_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import (
tracerhardlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/hardlink/types"
tracerandomx "github.com/kubescape/node-agent/pkg/ebpf/gadgets/randomx/tracer"
tracerandomxtype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/randomx/types"
tracerssh "github.com/kubescape/node-agent/pkg/ebpf/gadgets/ssh/tracer"
tracersshtype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/ssh/types"
tracersymlink "github.com/kubescape/node-agent/pkg/ebpf/gadgets/symlink/tracer"
tracersymlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/symlink/types"
"github.com/kubescape/node-agent/pkg/malwaremanager"
Expand All @@ -55,6 +57,7 @@ const (
randomxTraceName = "trace_randomx"
symlinkTraceName = "trace_symlink"
hardlinkTraceName = "trace_hardlink"
sshTraceName = "trace_ssh"
capabilitiesWorkerPoolSize = 1
execWorkerPoolSize = 2
openWorkerPoolSize = 8
Expand All @@ -63,6 +66,7 @@ const (
randomxWorkerPoolSize = 1
symlinkWorkerPoolSize = 1
hardlinkWorkerPoolSize = 1
sshWorkerPoolSize = 1
)

type IGContainerWatcher struct {
Expand Down Expand Up @@ -98,6 +102,7 @@ type IGContainerWatcher struct {
randomxTracer *tracerandomx.Tracer
symlinkTracer *tracersymlink.Tracer
hardlinkTracer *tracerhardlink.Tracer
sshTracer *tracerssh.Tracer
kubeIPInstance operators.OperatorInstance
kubeNameInstance operators.OperatorInstance

Expand All @@ -110,6 +115,7 @@ type IGContainerWatcher struct {
randomxWorkerPool *ants.PoolWithFunc
symlinkWorkerPool *ants.PoolWithFunc
hardlinkWorkerPool *ants.PoolWithFunc
sshdWorkerPool *ants.PoolWithFunc

capabilitiesWorkerChan chan *tracercapabilitiestype.Event
execWorkerChan chan *tracerexectype.Event
Expand All @@ -119,6 +125,7 @@ type IGContainerWatcher struct {
randomxWorkerChan chan *tracerandomxtype.Event
symlinkWorkerChan chan *tracersymlinktype.Event
hardlinkWorkerChan chan *tracerhardlinktype.Event
sshWorkerChan chan *tracersshtype.Event

preRunningContainersIDs mapset.Set[string]

Expand Down Expand Up @@ -293,6 +300,18 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli
if err != nil {
return nil, fmt.Errorf("creating hardlink worker pool: %w", err)
}
// Create a ssh worker pool
sshWorkerPool, err := ants.NewPoolWithFunc(sshWorkerPoolSize, func(i interface{}) {
event := i.(tracersshtype.Event)
if event.K8s.ContainerName == "" {
return
}
metrics.ReportEvent(utils.SSHEventType)
ruleManager.ReportSSHEvent(event)
})
if err != nil {
return nil, fmt.Errorf("creating ssh worker pool: %w", err)
}

return &IGContainerWatcher{
// Configuration
Expand Down Expand Up @@ -323,6 +342,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli
randomxWorkerPool: randomxWorkerPool,
symlinkWorkerPool: symlinkWorkerPool,
hardlinkWorkerPool: hardlinkWorkerPool,
sshdWorkerPool: sshWorkerPool,
metrics: metrics,
preRunningContainersIDs: preRunningContainers,

Expand All @@ -335,6 +355,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli
randomxWorkerChan: make(chan *tracerandomxtype.Event, 5000),
symlinkWorkerChan: make(chan *tracersymlinktype.Event, 1000),
hardlinkWorkerChan: make(chan *tracerhardlinktype.Event, 1000),
sshWorkerChan: make(chan *tracersshtype.Event, 1000),

// cache
ruleBindingPodNotify: ruleBindingPodNotify,
Expand Down
12 changes: 12 additions & 0 deletions pkg/containerwatcher/v1/container_watcher_private.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ func (ch *IGContainerWatcher) startTracers() error {
logger.L().Error("error starting hardlink tracing", helpers.Error(err))
return err
}

// NOTE: SSH tracing relies on the network tracer, so it must be started after the network tracer.
if err := ch.startSshTracing(); err != nil {
logger.L().Error("error starting ssh tracing", helpers.Error(err))
return err
}
}

return nil
Expand Down Expand Up @@ -322,6 +328,12 @@ func (ch *IGContainerWatcher) stopTracers() error {
logger.L().Error("error stopping hardlink tracing", helpers.Error(err))
errs = errors.Join(errs, err)
}

// Stop ssh tracer
if err := ch.stopSshTracing(); err != nil {
logger.L().Error("error stopping ssh tracing", helpers.Error(err))
errs = errors.Join(errs, err)
}
}

return errs
Expand Down
78 changes: 78 additions & 0 deletions pkg/containerwatcher/v1/ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package containerwatcher

import (
"fmt"

tracerssh "github.com/kubescape/node-agent/pkg/ebpf/gadgets/ssh/tracer"
tracersshtype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/ssh/types"

"github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection/networktracer"
"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
)

func (ch *IGContainerWatcher) sshEventCallback(event *tracersshtype.Event) {
if event.Type == types.DEBUG {
return
}

if isDroppedEvent(event.Type, event.Message) {
logger.L().Ctx(ch.ctx).Warning("ssh tracer got drop events - we may miss some realtime data", helpers.Interface("event", event), helpers.String("error", event.Message))
return
}

ch.containerCollection.EnrichByMntNs(&event.CommonData, event.MountNsID)
ch.containerCollection.EnrichByNetNs(&event.CommonData, event.NetNsID)

ch.sshWorkerChan <- event
}

func (ch *IGContainerWatcher) startSshTracing() error {
if err := ch.tracerCollection.AddTracer(sshTraceName, ch.containerSelector); err != nil {
return fmt.Errorf("adding tracer: %w", err)
}

tracerSsh, err := tracerssh.NewTracer()
if err != nil {
return fmt.Errorf("creating tracer: %w", err)
}
go func() {
for event := range ch.sshWorkerChan {
_ = ch.sshdWorkerPool.Invoke(*event)
}
}()

tracerSsh.SetSocketEnricherMap(ch.socketEnricher.SocketsMap())
tracerSsh.SetEventHandler(ch.sshEventCallback)

err = tracerSsh.RunWorkaround()
if err != nil {
return fmt.Errorf("running workaround: %w", err)
}

ch.sshTracer = tracerSsh

config := &networktracer.ConnectToContainerCollectionConfig[tracersshtype.Event]{
Tracer: ch.sshTracer,
Resolver: ch.containerCollection,
Selector: ch.containerSelector,
Base: tracersshtype.Base,
}

_, err = networktracer.ConnectToContainerCollection(config)
if err != nil {
return fmt.Errorf("creating tracer: %w", err)
}

return nil
}

func (ch *IGContainerWatcher) stopSshTracing() error {
// Stop ssh tracer
if err := ch.tracerCollection.RemoveTracer(sshTraceName); err != nil {
return fmt.Errorf("removing tracer: %w", err)
}
ch.sshTracer.Close()
return nil
}
108 changes: 108 additions & 0 deletions pkg/ebpf/gadgets/ssh/tracer/bpf/ssh.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/types.h>
#include <sys/socket.h>
#include <stdbool.h>

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#include "../../../../include/macros.h"
#include "../../../../include/buffer.h"

#define GADGET_TYPE_NETWORKING
#include "../../../../include/sockets-map.h"

#include "ssh.h"


// Events map.
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} events SEC(".maps");

// Empty event map.
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct event);
} empty_event SEC(".maps");

// we need this to make sure the compiler doesn't remove our struct.
const struct event *unusedevent __attribute__((unused));

SEC("socket")
int ssh_detector(struct __sk_buff *skb) {
// Check if it's an IP packet
if (skb->protocol != bpf_htons(ETH_P_IP))
return 0;

// Define the offset for IP header
int ip_offset = ETH_HLEN;

// Read IP header
struct iphdr iph;
if (bpf_skb_load_bytes(skb, ip_offset, &iph, sizeof(iph)) < 0)
return 0;

// Check if it's a TCP packet
if (iph.protocol != IPPROTO_TCP)
return 0;

// Calculate TCP header offset
int tcp_offset = ip_offset + (iph.ihl * 4);

// Read TCP header
struct tcphdr tcph;
if (bpf_skb_load_bytes(skb, tcp_offset, &tcph, sizeof(tcph)) < 0)
return 0;

// Calculate payload offset
int payload_offset = tcp_offset + (tcph.doff * 4);

// Read the first 4 bytes of the payload
char payload[SSH_SIG_LEN];
if (bpf_skb_load_bytes(skb, payload_offset, payload, SSH_SIG_LEN) < 0)
return 0;

// Check for SSH signature using memcmp
if (__builtin_memcmp(payload, SSH_SIGNATURE, SSH_SIG_LEN) == 0) {
struct event *event;
__u32 zero = 0;
event = bpf_map_lookup_elem(&empty_event, &zero);
if (!event) {
return 0;
}

// Enrich event with process metadata
struct sockets_value *skb_val = gadget_socket_lookup(skb);
if (skb_val != NULL) {
event->netns = skb->cb[0]; // cb[0] initialized by dispatcher.bpf.c
event->mntns_id = skb_val->mntns;
event->pid = skb_val->pid_tgid >> 32;
event->uid = (__u32)(skb_val->uid_gid);
event->gid = (__u32)(skb_val->uid_gid >> 32);
__builtin_memcpy(&event->comm, skb_val->task, sizeof(event->comm));

event->src_ip = iph.saddr;
event->dst_ip = iph.daddr;
event->src_port = bpf_ntohs(tcph.source);
event->dst_port = bpf_ntohs(tcph.dest);

event->timestamp = bpf_ktime_get_boot_ns();
}
__u64 skb_len = skb->len;
bpf_perf_event_output(skb, &events, skb_len << 32 | BPF_F_CURRENT_CPU, event, sizeof(struct event));
}

return 0;
}
Comment on lines +74 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amitschendel , if I understand correctly, this will fire an event for every SSH frame. I am not sure we need event per packet, maybe per connection.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take the above back. The SSH_ header is only there in the handshake messages (one that client sends to server, the second when the server sends to the client)

Still, will we get two alerts here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, this is why I am filtering out the response in the rule.



char LICENSE[] SEC("license") = "Dual BSD/GPL";
33 changes: 33 additions & 0 deletions pkg/ebpf/gadgets/ssh/tracer/bpf/ssh.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include "../../../../include/types.h"

#ifndef TASK_COMM_LEN
#define TASK_COMM_LEN 16
#endif
#define INVALID_UID ((uid_t)-1)
// Defined in include/uapi/linux/magic.h
#define OVERLAYFS_SUPER_MAGIC 0x794c7630
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#define SSH_SIGNATURE "SSH-"
#define SSH_SIG_LEN 4
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
#define ETH_HLEN 14

struct event {
// Keep netns at the top: networktracer depends on it
__u32 netns;

gadget_timestamp timestamp;
gadget_mntns_id mntns_id;
__u32 pid;
__u32 uid;
__u32 gid;
__u16 dst_port;
__u16 src_port;
__u32 dst_ip;
__u32 src_ip;
__u8 comm[TASK_COMM_LEN];
};
Loading
Loading