Skip to content

Commit

Permalink
new actionner kubernetes:sysdig
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Labarussias <issif+github@gadz.org>
  • Loading branch information
Issif committed Jan 10, 2025
1 parent 204e1e6 commit ee593f9
Show file tree
Hide file tree
Showing 8 changed files with 506 additions and 15 deletions.
2 changes: 2 additions & 0 deletions actionners/actionners.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
k8sLog "github.com/falcosecurity/falco-talon/actionners/kubernetes/log"
k8sNetworkpolicy "github.com/falcosecurity/falco-talon/actionners/kubernetes/networkpolicy"
k8sScript "github.com/falcosecurity/falco-talon/actionners/kubernetes/script"
k8sSysdig "github.com/falcosecurity/falco-talon/actionners/kubernetes/sysdig"
k8sTcpdump "github.com/falcosecurity/falco-talon/actionners/kubernetes/tcpdump"
k8sTerminate "github.com/falcosecurity/falco-talon/actionners/kubernetes/terminate"
"github.com/falcosecurity/falco-talon/configuration"
Expand Down Expand Up @@ -83,6 +84,7 @@ func ListDefaultActionners() *Actionners {
k8sDrain.Register(),
k8sDownload.Register(),
k8sTcpdump.Register(),
k8sSysdig.Register(),
lambdaInvoke.Register(),
gcpFunctionCall.Register(),
calicoNetworkpolicy.Register(),
Expand Down
5 changes: 2 additions & 3 deletions actionners/kubernetes/drain/drain.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package drain

import (
"context"
"fmt"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -158,7 +157,7 @@ func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, ac
nodeName := node.GetName()
objects["node"] = nodeName

pods, err := client.ListPods(context.Background(), metav1.ListOptions{
pods, err := client.ListPods(metav1.ListOptions{
FieldSelector: fmt.Sprintf("spec.nodeName=%s", nodeName),
})
if err != nil {
Expand Down Expand Up @@ -188,7 +187,7 @@ func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, ac
case <-stopListingDone:
return
case <-ticker.C:
pods2, err2 := client.ListPods(context.Background(), metav1.ListOptions{
pods2, err2 := client.ListPods(metav1.ListOptions{
FieldSelector: fmt.Sprintf("spec.nodeName=%s", nodeName),
})
if err2 != nil {
Expand Down
Binary file not shown.
259 changes: 259 additions & 0 deletions actionners/kubernetes/sysdig/sysdig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package sysdig

import (
"fmt"
"time"

"github.com/falcosecurity/falco-talon/internal/events"
k8sChecks "github.com/falcosecurity/falco-talon/internal/kubernetes/checks"
k8s "github.com/falcosecurity/falco-talon/internal/kubernetes/client"
"github.com/falcosecurity/falco-talon/internal/models"
"github.com/falcosecurity/falco-talon/internal/rules"
"github.com/falcosecurity/falco-talon/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
Name string = "sysdig"
Category string = "kubernetes"
Description string = "Capture the syscalls packets in a pod"
Source string = "syscalls"
Continue bool = false
UseContext bool = false
AllowOutput bool = false
RequireOutput bool = true
Permissions string = `apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: falco-talon
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- get
- create
`
Example string = `- action: Create a syscall capture from a pod
actionner: kubernetes:sysdig
parameters:
duration: 10
snaplen: 1024
output:
target: aws:s3
parameters:
bucket: my-bucket
prefix: /captures/
`
)

var (
RequiredOutputFields = []string{"k8s.ns.name", "k8s.pod.name"}
)

type Parameters struct {
Image string `mapstructure:"image"`
Duration int `mapstructure:"duration" validate:"gte=0"`
BufferSize int `mapstructure:"buffer_size" validate:"gte=0"`
}

const (
baseName string = "falco-talon-sysdig-"
defaultImage string = "sysdig/sysdig:latest"
defaultTTL int = 60
defaultDuration int = 5
defaultMaxDuration int = 30
defaultBufferSize int = 2048
)

type Actionner struct{}

func Register() *Actionner {
return new(Actionner)
}

func (a Actionner) Init() error {
return k8s.Init()
}

func (a Actionner) Information() models.Information {
return models.Information{
Name: Name,
FullName: Category + ":" + Name,
Category: Category,
Description: Description,
Source: Source,
RequiredOutputFields: RequiredOutputFields,
Permissions: Permissions,
Example: Example,
Continue: Continue,
AllowOutput: AllowOutput,
RequireOutput: RequireOutput,
}
}

func (a Actionner) Parameters() models.Parameters {
return Parameters{
Duration: defaultDuration,
BufferSize: defaultBufferSize,
Image: defaultImage,
}
}

func (a Actionner) Checks(event *events.Event, _ *rules.Action) error {
return k8sChecks.CheckPodExist(event)
}

func (a Actionner) Run(event *events.Event, action *rules.Action) (utils.LogLine, *models.Data, error) {
podName := event.GetPodName()
namespace := event.GetNamespaceName()

objects := map[string]string{
"pod": podName,
"namespace": namespace,
}

var parameters Parameters
err := utils.DecodeParams(action.GetParameters(), &parameters)
if err != nil {
return utils.LogLine{
Objects: nil,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

if parameters.Duration == 0 {
parameters.Duration = defaultDuration
}

if parameters.Duration > 30 {
parameters.Duration = defaultMaxDuration
}

if parameters.Image == "" {
parameters.Image = defaultImage
}

client := k8s.GetClient()

pod, _ := client.GetPod(podName, namespace)
containers := k8s.GetContainers(pod)
if len(containers) == 0 {
err = fmt.Errorf("no container found")
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

job, err := client.CreateJob("falco-talon-sysdig", namespace, parameters.Image, pod.Namespace, defaultTTL)
if err != nil {
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

objects["job"] = job

timeout := time.NewTimer(20 * time.Second)
ticker := time.NewTicker(300 * time.Millisecond)
defer timeout.Stop()
defer ticker.Stop()

var ready bool
var jPod, jContainer string
for !ready {
select {
case <-timeout.C:
err = fmt.Errorf("the job '%v' in the namespace '%v' for the sysdig capture is not ready", job, namespace)
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
case <-ticker.C:
p, err := client.ListPods(metav1.ListOptions{LabelSelector: "batch.kubernetes.io/job-name=" + job})
if err != nil {
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}
if len(p.Items) > 0 {
if p.Items[0].Status.Phase == corev1.PodRunning && p.Items[0].Status.ContainerStatuses[0].Ready {
jPod = p.Items[0].Name
jContainer = p.Items[0].Spec.Containers[0].Name
ready = true
}
}
}
}

command := []string{"tee", "/tmp/talon-script.sh", "/dev/null"}
script := fmt.Sprintf("sysdig --modern-bpf --cri /host/run/containerd/containerd.sock -M %v -s %v -z -w /tmp/sysdig.scap.gz || [ $? -eq 0 ] && echo OK || exit 1\n", parameters.Duration, parameters.BufferSize)
// script := fmt.Sprintf("sysdig --modern-bpf --cri /host/run/containerd.sock --cri /host/run/docker.sock --cri /host/run/crio.sock -M %v -s %v -z -w /tmp/sysdig.scap.gz || [ $? -eq 1 ] && echo OK || exit 1\n", parameters.Duration, parameters.BufferSize)
_, err = client.Exec(namespace, jPod, jContainer, command, script)
if err != nil {
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

command = []string{"sh", "/tmp/talon-script.sh"}
_, err = client.Exec(namespace, jPod, jContainer, command, "")
if err != nil {
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

command = []string{"cat", "/tmp/sysdig.scap.gz"}
output, err := client.Exec(namespace, jPod, jContainer, command, "")
if err != nil {
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

return utils.LogLine{
Objects: objects,
Output: fmt.Sprintf("a sysdig capture '%v' has been created", "sysdig.scap.gz"),
Status: utils.SuccessStr,
}, &models.Data{Name: "sysdig.scap.gz", Objects: objects, Bytes: output.Bytes()}, nil
}

func (a Actionner) CheckParameters(action *rules.Action) error {
var parameters Parameters

err := utils.DecodeParams(action.GetParameters(), &parameters)
if err != nil {
return err
}

err = utils.ValidateStruct(parameters)
if err != nil {
return err
}

return nil
}
15 changes: 8 additions & 7 deletions actionners/kubernetes/tcpdump/tcpdump.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ rules:
- get
- create
`
Example string = `- action: Get logs of the pod
Example string = `- action: Create a packet capture from a pod
actionner: kubernetes:tcpdump
parameters:
duration: 10
Expand All @@ -77,8 +77,9 @@ type Parameters struct {
const (
baseName string = "falco-talon-tcpdump-"
defaultImage string = "issif/tcpdump:latest"
defaultTTL int = 300
defaultTTL int = 120
defaultDuration int = 5
defaulSnaplen int = 4096
)

type Actionner struct{}
Expand Down Expand Up @@ -109,9 +110,9 @@ func (a Actionner) Information() models.Information {

func (a Actionner) Parameters() models.Parameters {
return Parameters{
Duration: 20,
Snaplen: 4096,
Image: "issif/tcpdump:latest",
Duration: defaultDuration,
Snaplen: defaulSnaplen,
Image: defaultImage,
}
}

Expand Down Expand Up @@ -170,8 +171,8 @@ func (a Actionner) Run(event *events.Event, action *rules.Action) (utils.LogLine
}, nil, err
}

command := []string{"tee", "/tmp/talon-script.sh", ">", "/dev/null"}
script := fmt.Sprintf("timeout %vs tcpdump -n -i any -s %v -w /tmp/tcpdump.pcap || [ $? -eq 124 ] && echo OK || exit 1", parameters.Duration, parameters.Snaplen)
command := []string{"tee", "/tmp/talon-script.sh", "/dev/null"}
script := fmt.Sprintf("timeout %vs tcpdump -n -i any -s %v -w /tmp/tcpdump.pcap || [ $? -eq 124 ] && echo OK || exit 1\n", parameters.Duration, parameters.Snaplen)
_, err = client.Exec(namespace, podName, ephemeralContainerName, command, script)
if err != nil {
return utils.LogLine{
Expand Down
Loading

0 comments on commit ee593f9

Please sign in to comment.