diff --git a/actionners/actionners.go b/actionners/actionners.go index 05757eb7..22073982 100644 --- a/actionners/actionners.go +++ b/actionners/actionners.go @@ -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" @@ -83,6 +84,7 @@ func ListDefaultActionners() *Actionners { k8sDrain.Register(), k8sDownload.Register(), k8sTcpdump.Register(), + k8sSysdig.Register(), lambdaInvoke.Register(), gcpFunctionCall.Register(), calicoNetworkpolicy.Register(), @@ -110,7 +112,7 @@ func Init() error { for _, actionner := range *defaultActionners { if category == actionner.Information().Category { if err := actionner.Init(); err != nil { - utils.PrintLog("error", utils.LogLine{Message: "init", Error: err.Error(), Category: actionner.Information().Category, Status: utils.FailureStr}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Message: "init", Error: err.Error(), Category: actionner.Information().Category, Status: utils.FailureStr}) return err } enabledCategories[category] = true @@ -174,7 +176,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve if rule.DryRun == trueStr { log.Output = "no action, dry-run is enabled" - utils.PrintLog("info", log) + utils.PrintLog(utils.InfoStr, log) return err } @@ -182,7 +184,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve if actionner == nil { log.Status = utils.FailureStr log.Error = fmt.Sprintf("unknown actionner '%v'", action.GetActionner()) - utils.PrintLog("error", log) + utils.PrintLog(utils.ErrorStr, log) return fmt.Errorf("unknown actionner '%v'", action.GetActionner()) } @@ -192,7 +194,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve if err2 := actionner.Checks(event, action); err2 != nil { log.Status = utils.FailureStr log.Error = err2.Error() - utils.PrintLog("error", log) + utils.PrintLog(utils.ErrorStr, log) span.SetStatus(codes.Error, err2.Error()) span.RecordError(err2) span.End() @@ -223,6 +225,11 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve trace.WithAttributes(attribute.String("actionner.name", action.GetActionnerName())), ) defer span.End() + + logP := log + logP.Status = utils.InProgressStr + utils.PrintLog(utils.InfoStr, logP) + result, data, err := actionner.Run(event, action) span.SetAttributes(attribute.String("action.result", result.Status)) span.SetAttributes(attribute.String("action.output", result.Output)) @@ -255,7 +262,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve log.Error = err.Error() span.SetStatus(codes.Error, err.Error()) span.RecordError(err) - utils.PrintLog("error", log) + utils.PrintLog(utils.ErrorStr, log) go notifiers.Notify(actx, rule, action, event, log) return err } @@ -263,7 +270,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve span.AddEvent(result.Output) span.SetStatus(codes.Ok, "action successfully completed") - utils.PrintLog("info", log) + utils.PrintLog(utils.InfoStr, log) go notifiers.Notify(actx, rule, action, event, log) if actionner.Information().RequireOutput { @@ -280,7 +287,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve logO.Status = utils.FailureStr logO.Error = err.Error() logO.OutputTarget = "n/a" - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) metrics.IncreaseCounter(logO) span.SetStatus(codes.Error, err.Error()) span.RecordError(err) @@ -293,7 +300,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve err = fmt.Errorf("empty output") logO.Status = utils.FailureStr logO.Error = err.Error() - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) metrics.IncreaseCounter(logO) span.SetStatus(codes.Error, err.Error()) span.RecordError(err) @@ -309,7 +316,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve logO.Status = utils.FailureStr logO.OutputTarget = target logO.Error = err.Error() - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) metrics.IncreaseCounter(logO) span.SetAttributes(attribute.String("output.target", target)) span.SetStatus(codes.Error, err.Error()) @@ -329,7 +336,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve if err2 := o.Checks(output); err2 != nil { logO.Status = utils.FailureStr logO.Error = err2.Error() - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) metrics.IncreaseCounter(logO) span.SetStatus(codes.Error, err2.Error()) span.RecordError(err2) @@ -355,7 +362,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve if err != nil { logO.Error = err.Error() - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) span.SetStatus(codes.Error, err.Error()) span.RecordError(err) go notifiers.Notify(octx, rule, action, event, logO) @@ -365,7 +372,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve span.SetStatus(codes.Ok, "output successfully completed") span.AddEvent(result.Output) - utils.PrintLog("info", logO) + utils.PrintLog(utils.InfoStr, logO) go notifiers.Notify(octx, rule, action, event, logO) span.End() return nil @@ -388,7 +395,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve logO.OutputTarget = target logO.Status = utils.FailureStr logO.Error = err.Error() - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) span.SetAttributes(attribute.String("output.target", target)) span.SetStatus(codes.Error, err.Error()) span.RecordError(err) @@ -408,7 +415,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve err = fmt.Errorf("empty output") logO.Status = utils.FailureStr logO.Error = err.Error() - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) metrics.IncreaseCounter(logO) span.SetStatus(codes.Error, err.Error()) span.RecordError(err) @@ -434,7 +441,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve if err != nil { logO.Error = err.Error() - utils.PrintLog("error", logO) + utils.PrintLog(utils.ErrorStr, logO) span.SetStatus(codes.Error, err.Error()) span.RecordError(err) go notifiers.Notify(octx, rule, action, event, logO) @@ -444,7 +451,7 @@ func runAction(mctx context.Context, rule *rules.Rule, action *rules.Action, eve span.SetStatus(codes.Ok, "output successfully completed") span.AddEvent(result.Output) - utils.PrintLog("info", logO) + utils.PrintLog(utils.InfoStr, logO) go notifiers.Notify(octx, rule, action, event, logO) span.End() return nil @@ -490,7 +497,7 @@ func StartConsumer(eventsC <-chan nats.MessageWithContext) { } if !config.PrintAllEvents { - utils.PrintLog("info", log) + utils.PrintLog(utils.InfoStr, log) } for _, i := range triggeredRules { @@ -510,7 +517,7 @@ func StartConsumer(eventsC <-chan nats.MessageWithContext) { span.SetStatus(codes.Ok, "match detected") span.End() - utils.PrintLog("info", log) + utils.PrintLog(utils.InfoStr, log) metrics.IncreaseCounter(log) for _, a := range i.GetActions() { @@ -531,7 +538,7 @@ func StartConsumer(eventsC <-chan nats.MessageWithContext) { TraceID: e.TraceID, Error: err.Error(), } - utils.PrintLog("error", log) + utils.PrintLog(utils.ErrorStr, log) if a.IgnoreErrors != trueStr { break } diff --git a/actionners/cilium/networkpolicy/networkpolicy.go b/actionners/cilium/networkpolicy/networkpolicy.go index e55bc42a..95920f25 100644 --- a/actionners/cilium/networkpolicy/networkpolicy.go +++ b/actionners/cilium/networkpolicy/networkpolicy.go @@ -80,11 +80,11 @@ rules: actionner: cilium:networkpolicy parameters: allow_cidr: - - "192.168.1.0/24" - - "172.17.0.0/16" + - "192.168.1.0/24" + - "172.17.0.0/16" allow_namespaces: - - "green-ns" - - "blue-ns" + - "green-ns" + - "blue-ns" ` ) diff --git a/actionners/gcp/function/function.go b/actionners/gcp/function/function.go index bc5a4d59..e1f107ea 100644 --- a/actionners/gcp/function/function.go +++ b/actionners/gcp/function/function.go @@ -30,8 +30,8 @@ const ( AllowOutput bool = false RequireOutput bool = false Permissions string = `{ - "cloudfunctions.functions.get", - "cloudfunctions.functions.invoke" + "cloudfunctions.functions.get", + "cloudfunctions.functions.invoke" }` Example string = `- action: Invoke GCP Cloud Function actionner: gcp:function @@ -39,7 +39,7 @@ const ( gcp_function_name: sample-function gcp_function_location: us-central1 gcp_function_timeout: 10 - ` +` ) var ( diff --git a/actionners/kubernetes/annotation/annotation.go b/actionners/kubernetes/annotation/annotation.go index 096a2751..08468734 100644 --- a/actionners/kubernetes/annotation/annotation.go +++ b/actionners/kubernetes/annotation/annotation.go @@ -23,7 +23,7 @@ const ( Name string = "annotation" Category string = "kubernetes" Description string = "Add, modify or delete the annotations of the pod/node" - Source string = "syscalls" + Source string = "syscalls, k8s_audit" Continue bool = true UseContext bool = false AllowOutput bool = false diff --git a/actionners/kubernetes/cordon/cordon.go b/actionners/kubernetes/cordon/cordon.go index 11e049da..d4a93296 100644 --- a/actionners/kubernetes/cordon/cordon.go +++ b/actionners/kubernetes/cordon/cordon.go @@ -19,7 +19,7 @@ const ( Name string = "cordon" Category string = "kubernetes" Description string = "Cordon a node" - Source string = "syscalls" + Source string = "syscalls, k8s_audit" Continue bool = true UseContext bool = false AllowOutput bool = false diff --git a/actionners/kubernetes/delete/delete.go b/actionners/kubernetes/delete/delete.go index b2fd32d6..0da27d21 100644 --- a/actionners/kubernetes/delete/delete.go +++ b/actionners/kubernetes/delete/delete.go @@ -19,7 +19,7 @@ const ( Name string = "delete" Category string = "kubernetes" Description string = "Delete a resource" - Source string = "k8saudit" + Source string = "k8s_audit" Continue bool = false UseContext bool = false AllowOutput bool = false diff --git a/actionners/kubernetes/drain/drain.go b/actionners/kubernetes/drain/drain.go index 80ca00d4..838326a5 100644 --- a/actionners/kubernetes/drain/drain.go +++ b/actionners/kubernetes/drain/drain.go @@ -1,7 +1,6 @@ package drain import ( - "context" "fmt" "sync" "sync/atomic" @@ -24,8 +23,8 @@ import ( const ( Name string = "drain" Category string = "kubernetes" - Description string = "Drain a pod" - Source string = "syscalls" + Description string = "Drain a node" + Source string = "syscalls, k8s_audit" Continue bool = true UseContext bool = false AllowOutput bool = false @@ -119,7 +118,7 @@ func (a Actionner) Run(event *events.Event, action *rules.Action) (utils.LogLine return a.RunWithClient(*client, event, action) } -func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, action *rules.Action) (utils.LogLine, *models.Data, error) { +func (a Actionner) RunWithClient(client k8s.Client, event *events.Event, action *rules.Action) (utils.LogLine, *models.Data, error) { podName := event.GetPodName() namespace := event.GetNamespaceName() objects := map[string]string{} @@ -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 { @@ -188,11 +187,11 @@ 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 { - utils.PrintLog("warning", utils.LogLine{Message: fmt.Sprintf("error listing pods on node '%v': %v", nodeName, err2)}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Message: fmt.Sprintf("error listing pods on node '%v': %v", nodeName, err2)}) continue } @@ -235,20 +234,20 @@ func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, ac case utils.ReplicaSetStr: replicaSetName, err := k8s.GetOwnerName(p) if err != nil { - utils.PrintLog("warning", utils.LogLine{Message: fmt.Sprintf("error getting pod owner name: %v", err)}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Message: fmt.Sprintf("error getting pod owner name: %v", err)}) atomic.AddInt32(&otherErrorsCount, 1) return } if parameters.MinHealthyReplicas != "" { replicaSet, err := client.GetReplicaSet(replicaSetName, p.Namespace) if err != nil { - utils.PrintLog("warning", utils.LogLine{Message: fmt.Sprintf("error getting replica set for pod '%v': %v", p.Name, err)}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Message: fmt.Sprintf("error getting replica set for pod '%v': %v", p.Name, err)}) atomic.AddInt32(&otherErrorsCount, 1) return } minHealthyReplicasValue, kind, err := helpers.ParseMinHealthyReplicas(parameters.MinHealthyReplicas) if err != nil { - utils.PrintLog("warning", utils.LogLine{Message: fmt.Sprintf("error parsing min_healthy_replicas: %v", err)}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Message: fmt.Sprintf("error parsing min_healthy_replicas: %v", err)}) atomic.AddInt32(&otherErrorsCount, 1) return } @@ -256,7 +255,7 @@ func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, ac case "absolut": healthyReplicasCount, err := k8s.GetHealthyReplicasCount(replicaSet) if err != nil { - utils.PrintLog("warning", utils.LogLine{Message: fmt.Sprintf("error getting health replicas count for pod '%v': %v", p.Name, err)}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Message: fmt.Sprintf("error getting health replicas count for pod '%v': %v", p.Name, err)}) atomic.AddInt32(&otherErrorsCount, 1) return } @@ -268,7 +267,7 @@ func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, ac healthyReplicasValue, err := k8s.GetHealthyReplicasCount(replicaSet) minHealthyReplicasAbsoluteValue := int64(float64(minHealthyReplicasValue) / 100.0 * float64(healthyReplicasValue)) if err != nil { - utils.PrintLog("warning", utils.LogLine{Message: fmt.Sprintf("error getting health replicas count for pod '%v': %v", p.Name, err)}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Message: fmt.Sprintf("error getting health replicas count for pod '%v': %v", p.Name, err)}) atomic.AddInt32(&otherErrorsCount, 1) return } @@ -281,7 +280,7 @@ func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, ac } if err := client.EvictPod(p); err != nil { - utils.PrintLog("warning", utils.LogLine{Message: fmt.Sprintf("error evicting pod '%v': %v", p.Name, err)}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Message: fmt.Sprintf("error evicting pod '%v': %v", p.Name, err)}) atomic.AddInt32(&evictionErrorsCount, 1) return } @@ -294,7 +293,7 @@ func (a Actionner) RunWithClient(client k8s.DrainClient, event *events.Event, ac for { select { case <-timeout: - utils.PrintLog("error", utils.LogLine{Message: fmt.Sprintf("pod '%v' did not terminate within the max_wait_period", pod.Name)}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Message: fmt.Sprintf("pod '%v' did not terminate within the max_wait_period", pod.Name)}) atomic.AddInt32(&evictionWaitPeriodErrorsCount, 1) return diff --git a/actionners/kubernetes/exec/exec.go b/actionners/kubernetes/exec/exec.go index ff70d9ff..4ab0a464 100644 --- a/actionners/kubernetes/exec/exec.go +++ b/actionners/kubernetes/exec/exec.go @@ -17,7 +17,7 @@ const ( Name string = "exec" Category string = "kubernetes" Description string = "Exec a command in a pod" - Source string = "syscalls" + Source string = "syscalls, k8s_audit" Continue bool = true UseContext bool = true AllowOutput bool = false diff --git a/actionners/kubernetes/label/label.go b/actionners/kubernetes/label/label.go index ff1c84c5..3ed92c17 100644 --- a/actionners/kubernetes/label/label.go +++ b/actionners/kubernetes/label/label.go @@ -23,7 +23,7 @@ const ( Name string = "label" Category string = "kubernetes" Description string = "Add, modify or delete the labels of the pod/node" - Source string = "syscalls" + Source string = "syscalls, k8s_audit" Continue bool = true UseContext bool = false AllowOutput bool = false diff --git a/actionners/kubernetes/log/log.go b/actionners/kubernetes/log/log.go index 489cc5b7..c580f3fc 100644 --- a/actionners/kubernetes/log/log.go +++ b/actionners/kubernetes/log/log.go @@ -20,7 +20,7 @@ const ( Name string = "log" Category string = "kubernetes" Description string = "Get logs from a pod" - Source string = "syscalls" + Source string = "syscalls, k8s_audit" Continue bool = true UseContext bool = false AllowOutput bool = true diff --git a/actionners/kubernetes/script/script.go b/actionners/kubernetes/script/script.go index 8b03c2ed..60869468 100644 --- a/actionners/kubernetes/script/script.go +++ b/actionners/kubernetes/script/script.go @@ -18,7 +18,7 @@ const ( Name string = "script" Category string = "kubernetes" Description string = "Run a script in a pod" - Source string = "syscalls" + Source string = "syscalls, k8s_audit" Continue bool = true UseContext bool = true AllowOutput bool = false diff --git a/actionners/kubernetes/sysdig/sysdig.go b/actionners/kubernetes/sysdig/sysdig.go new file mode 100644 index 00000000..fb9bad2b --- /dev/null +++ b/actionners/kubernetes/sysdig/sysdig.go @@ -0,0 +1,279 @@ +package sysdig + +import ( + "fmt" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "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" +) + +const ( + Name string = "sysdig" + Category string = "kubernetes" + Description string = "Capture the syscalls packets in a pod" + Source string = "syscalls, k8s_audit" + 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 +- apiGroups: + - "batch" + resources: + - jobs + verbs: + - get + - list + - create +` + Example string = `- action: Create a syscall capture from a pod + actionner: kubernetes:sysdig + parameters: + duration: 10 + buffer_size: 1024 + output: + target: aws:s3 + parameters: + bucket: my-bucket + prefix: /captures/ +` +) + +var ( + RequiredOutputFields = []string{"k8s.ns.name, k8s.pod.name", "ka.target.namespace, (ka.target.pod.name or ka.target.name)"} +) + +type Parameters struct { + Image string `mapstructure:"image"` + Scope string `mapstructure:"scope" validate:"oneof=pod node"` + Duration int `mapstructure:"duration" validate:"gte=0,lte=30"` + BufferSize int `mapstructure:"buffer_size" validate:"gte=128"` +} + +const ( + baseName string = "falco-talon-sysdig-" + defaultImage string = "issif/sysdig:latest" + defaultScope string = "pod" + 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, + Scope: defaultScope, + 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(), ¶meters) + 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.Image == "" { + parameters.Image = defaultImage + } + + if parameters.Scope == "" { + parameters.Scope = defaultScope + } + + 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.Spec.NodeName, 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, err2 := client.ListPods(metav1.ListOptions{LabelSelector: "batch.kubernetes.io/job-name=" + job}) + if err2 != nil { + return utils.LogLine{ + Objects: objects, + Error: err2.Error(), + Status: utils.FailureStr, + }, nil, err2 + } + 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"} + sysdigCmd := fmt.Sprintf("sysdig --modern-bpf --cri /run/containerd/containerd.sock -M %v -s %v -z -w /tmp/sysdig.scap.gz", parameters.Duration, parameters.BufferSize) + if parameters.Scope == "pod" { + containers := []string{} + for _, i := range pod.Status.ContainerStatuses { + containers = append(containers, strings.ReplaceAll(i.ContainerID, "containerd://", "")[:12]) + } + sysdigCmd = fmt.Sprintf("%v \"container.id in (%v)\"", sysdigCmd, strings.Join(containers, ",")) + } + script := fmt.Sprintf("%v || [ $? -eq 0 ] && echo OK || exit 1\n", sysdigCmd) + _, 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(), ¶meters) + if err != nil { + return err + } + + err = utils.ValidateStruct(parameters) + if err != nil { + return err + } + + return nil +} diff --git a/actionners/kubernetes/tcpdump/tcpdump.go b/actionners/kubernetes/tcpdump/tcpdump.go index 2caa7e00..993dfcac 100644 --- a/actionners/kubernetes/tcpdump/tcpdump.go +++ b/actionners/kubernetes/tcpdump/tcpdump.go @@ -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 @@ -77,8 +77,9 @@ type Parameters struct { const ( baseName string = "falco-talon-tcpdump-" defaultImage string = "issif/tcpdump:latest" - defaultTTL int = 300 + defaultTTL int = 60 defaultDuration int = 5 + defaulSnaplen int = 4096 ) type Actionner struct{} @@ -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, } } @@ -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{ diff --git a/cmd/root.go b/cmd/root.go index 175d1a72..68b0733b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,7 +21,7 @@ no-code solution. With easy rules, you can perform actions over compromised pods func Execute() { err := RootCmd.Execute() if err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error()}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error()}) } } diff --git a/cmd/rules.go b/cmd/rules.go index 4961a174..9f4cc65d 100644 --- a/cmd/rules.go +++ b/cmd/rules.go @@ -34,7 +34,7 @@ var rulesChecksCmd = &cobra.Command{ } rules := ruleengine.ParseRules(config.RulesFiles) if rules == nil { - utils.PrintLog("fatal", utils.LogLine{Error: "invalid rules", Message: "rules"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: "invalid rules", Message: "rules"}) } defaultActionners := actionners.ListDefaultActionners() defaultOutputs := outputs.ListDefaultOutputs() @@ -45,36 +45,36 @@ var rulesChecksCmd = &cobra.Command{ for _, j := range i.GetActions() { actionner := defaultActionners.FindActionner(j.GetActionner()) if actionner == nil { - utils.PrintLog("error", utils.LogLine{Error: "unknown actionner", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "unknown actionner", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false continue } if err := actionner.CheckParameters(j); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false } o := j.GetOutput() if o == nil && actionner.Information().RequireOutput { - utils.PrintLog("error", utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false } if actionner != nil { o := j.GetOutput() if o == nil && actionner.Information().RequireOutput { - utils.PrintLog("error", utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false } if o != nil { output := defaultOutputs.FindOutput(o.GetTarget()) if output == nil { - utils.PrintLog("error", utils.LogLine{Error: "unknown target", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "unknown target", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } else if len(o.Parameters) == 0 { - utils.PrintLog("error", utils.LogLine{Error: "missing parameters for the output", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "missing parameters for the output", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } else { if err := output.CheckParameters(o); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } } @@ -84,9 +84,9 @@ var rulesChecksCmd = &cobra.Command{ } } if !valid { - utils.PrintLog("fatal", utils.LogLine{Error: "invalid rules", Message: "rules"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: "invalid rules", Message: "rules"}) } - utils.PrintLog("info", utils.LogLine{Result: "rules file valid", Message: "rules"}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Result: "rules file valid", Message: "rules"}) }, } @@ -104,7 +104,7 @@ var rulesPrintCmd = &cobra.Command{ } rules := ruleengine.ParseRules(config.RulesFiles) if rules == nil { - utils.PrintLog("fatal", utils.LogLine{Error: "invalid rules", Message: "rules"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: "invalid rules", Message: "rules"}) } type yamlFile struct { Name string `yaml:"rule"` @@ -136,7 +136,7 @@ var rulesPrintCmd = &cobra.Command{ var q []yamlFile if err := copier.Copy(&q, &rules); err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error()}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error()}) } b, _ := yaml.Marshal(q) diff --git a/cmd/server.go b/cmd/server.go index 5afaf8c5..ea2ca2cf 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -40,7 +40,7 @@ var serverCmd = &cobra.Command{ } rules := ruleengine.ParseRules(config.RulesFiles) if rules == nil { - utils.PrintLog("fatal", utils.LogLine{Error: "invalid rules", Message: "rules"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: "invalid rules", Message: "rules"}) } defaultActionners := actionners.ListDefaultActionners() @@ -52,31 +52,31 @@ var serverCmd = &cobra.Command{ for _, j := range i.GetActions() { actionner := defaultActionners.FindActionner(j.GetActionner()) if actionner == nil { - utils.PrintLog("error", utils.LogLine{Error: "unknown actionner", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "unknown actionner", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false } else { if err := actionner.CheckParameters(j); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false } } if actionner != nil { o := j.GetOutput() if o == nil && actionner.Information().RequireOutput { - utils.PrintLog("error", utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false } if o != nil { output := defaultOutputs.FindOutput(o.GetTarget()) if output == nil { - utils.PrintLog("error", utils.LogLine{Error: "unknown target", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "unknown target", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } else if len(o.Parameters) == 0 { - utils.PrintLog("error", utils.LogLine{Error: "missing parameters for the output", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "missing parameters for the output", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } else { if err := output.CheckParameters(o); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } } @@ -86,28 +86,28 @@ var serverCmd = &cobra.Command{ } } if !valid { - utils.PrintLog("fatal", utils.LogLine{Error: "invalid rules", Message: "rules"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: "invalid rules", Message: "rules"}) } // init actionners if err := actionners.Init(); err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error(), Message: "actionners"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error(), Message: "actionners"}) } // init outputs if err := outputs.Init(); err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error(), Message: "outputs"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error(), Message: "outputs"}) } // init notifiers notifiers.Init() if rules != nil { - utils.PrintLog("info", utils.LogLine{Result: fmt.Sprintf("%v rule(s) has/have been successfully loaded", len(*rules)), Message: "init"}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Result: fmt.Sprintf("%v rule(s) has/have been successfully loaded", len(*rules)), Message: "init"}) } if config.WatchRules { - utils.PrintLog("info", utils.LogLine{Result: "watch of rules enabled", Message: "init"}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Result: "watch of rules enabled", Message: "init"}) } srv := http.Server{ @@ -122,13 +122,13 @@ var serverCmd = &cobra.Command{ ignore := false watcher, err := fsnotify.NewWatcher() if err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Message: "rules"}) return } defer watcher.Close() for _, i := range config.RulesFiles { if err := watcher.Add(i); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Message: "rules"}) return } } @@ -141,10 +141,10 @@ var serverCmd = &cobra.Command{ time.Sleep(1 * time.Second) ignore = false }() - utils.PrintLog("info", utils.LogLine{Result: "changes detected", Message: "rules"}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Result: "changes detected", Message: "rules"}) newRules := ruleengine.ParseRules(config.RulesFiles) if newRules == nil { - utils.PrintLog("error", utils.LogLine{Error: "invalid rules", Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "invalid rules", Message: "rules"}) break } @@ -160,46 +160,46 @@ var serverCmd = &cobra.Command{ break } if err := actionner.CheckParameters(j); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Rule: i.GetName(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Rule: i.GetName(), Message: "rules"}) valid = false } o := j.GetOutput() if o == nil && actionner.Information().RequireOutput { - utils.PrintLog("error", utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "an output is required", Rule: i.GetName(), Action: j.GetName(), Actionner: j.GetActionner(), Message: "rules"}) valid = false } if o != nil { output := defaultOutputs.FindOutput(o.GetTarget()) if output == nil { - utils.PrintLog("error", utils.LogLine{Error: "unknown target", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "unknown target", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } if len(o.Parameters) == 0 { - utils.PrintLog("error", utils.LogLine{Error: "missing parameters for the output", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "missing parameters for the output", Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } if err := output.CheckParameters(o); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Rule: i.GetName(), Action: j.GetName(), OutputTarget: o.GetTarget(), Message: "rules"}) valid = false } } } if !valid { - utils.PrintLog("error", utils.LogLine{Error: "invalid rules", Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "invalid rules", Message: "rules"}) break } - utils.PrintLog("info", utils.LogLine{Result: fmt.Sprintf("%v rules have been successfully loaded", len(*rules)), Message: "rules"}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Result: fmt.Sprintf("%v rules have been successfully loaded", len(*rules)), Message: "rules"}) rules = newRules if err := actionners.Init(); err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Message: "actionners"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Message: "actionners"}) break } } } } case err := <-watcher.Errors: - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Message: "rules"}) } } }() @@ -207,7 +207,7 @@ var serverCmd = &cobra.Command{ // start the local NATS ns, err := nats.StartServer(config.Deduplication.TimeWindowSeconds) if err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error(), Message: "nats"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error(), Message: "nats"}) } defer ns.Shutdown() @@ -216,21 +216,21 @@ var serverCmd = &cobra.Command{ go func() { err2 := k8s.Init() if err2 != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err2.Error(), Message: "lease"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err2.Error(), Message: "lease"}) } c, err2 := k8s.GetClient().GetLeaseHolder() if err2 != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err2.Error(), Message: "lease"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err2.Error(), Message: "lease"}) } for { s := <-c if s == *utils.GetLocalIP() { s = "127.0.0.1" } - utils.PrintLog("info", utils.LogLine{Result: fmt.Sprintf("new leader detected '%v'", s), Message: "nats"}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Result: fmt.Sprintf("new leader detected '%v'", s), Message: "nats"}) err2 = nats.GetPublisher().SetJetStreamContext("nats://" + s + ":4222") if err2 != nil { - utils.PrintLog("error", utils.LogLine{Error: err2.Error(), Message: "nats"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err2.Error(), Message: "nats"}) } } }() @@ -240,27 +240,27 @@ var serverCmd = &cobra.Command{ c, err := nats.GetConsumer().ConsumeMsg() if err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error(), Message: "nats"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error(), Message: "nats"}) } go actionners.StartConsumer(c) - utils.PrintLog("info", utils.LogLine{Result: fmt.Sprintf("Falco Talon is up and listening on %s:%d", config.ListenAddress, config.ListenPort), Message: "http"}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Result: fmt.Sprintf("Falco Talon is up and listening on %s:%d", config.ListenAddress, config.ListenPort), Message: "http"}) ctx := context.Background() otelShutdown, err := traces.SetupOTelSDK(ctx) if err != nil { - utils.PrintLog("warn", utils.LogLine{Error: err.Error(), Message: "otel-traces"}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Error: err.Error(), Message: "otel-traces"}) } defer func() { if err := otelShutdown(ctx); err != nil { - utils.PrintLog("warn", utils.LogLine{Error: err.Error(), Message: "otel-traces"}) + utils.PrintLog(utils.WarningStr, utils.LogLine{Error: err.Error(), Message: "otel-traces"}) } }() metrics.Init() if err := srv.ListenAndServe(); err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error(), Message: "http"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error(), Message: "http"}) } }, } diff --git a/configuration/configuration.go b/configuration/configuration.go index bc5b381e..ec6904ef 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -107,12 +107,12 @@ func CreateConfiguration(configFile string) *Configuration { v.SetConfigFile(configFile) err := v.ReadInConfig() if err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: fmt.Sprintf("error when reading config file: '%v'", err.Error()), Message: "config"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: fmt.Sprintf("error when reading config file: '%v'", err.Error()), Message: "config"}) } } if err := v.Unmarshal(config); err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: fmt.Sprintf("error unmarshalling config file: '%v'", err.Error()), Message: "config"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: fmt.Sprintf("error unmarshalling config file: '%v'", err.Error()), Message: "config"}) } return config diff --git a/internal/aws/client/client.go b/internal/aws/client/client.go index d1c98011..aaa9279f 100644 --- a/internal/aws/client/client.go +++ b/internal/aws/client/client.go @@ -79,7 +79,7 @@ func Init() error { } if initErr == nil { - utils.PrintLog("info", utils.LogLine{Message: "init", Category: "aws", Status: utils.SuccessStr}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Message: "init", Category: "aws", Status: utils.SuccessStr}) } }) diff --git a/internal/events/events.go b/internal/events/events.go index 158690aa..1fa64765 100644 --- a/internal/events/events.go +++ b/internal/events/events.go @@ -58,6 +58,12 @@ func (event *Event) GetPodName() string { if event.OutputFields["k8s.pod.name"] != nil { return event.OutputFields["k8s.pod.name"].(string) } + if event.OutputFields["ka.target.pod.name"] != nil { + return event.OutputFields["ka.target.pod.name"].(string) + } + if event.OutputFields["ka.target.name"] != nil { + return event.OutputFields["ka.target.name"].(string) + } return "" } @@ -65,6 +71,9 @@ func (event *Event) GetNamespaceName() string { if event.OutputFields["k8s.ns.name"] != nil { return event.OutputFields["k8s.ns.name"].(string) } + if event.OutputFields["ka.target.namespace"] != nil { + return event.OutputFields["ka.target.namespace"].(string) + } return "" } diff --git a/internal/gcp/client/client.go b/internal/gcp/client/client.go index 49b1f434..7c938daa 100644 --- a/internal/gcp/client/client.go +++ b/internal/gcp/client/client.go @@ -99,7 +99,7 @@ func Init() error { httpClient: &http.Client{}, } - utils.PrintLog("info", utils.LogLine{Message: "init", Category: "gcp", Status: utils.SuccessStr}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Message: "init", Category: "gcp", Status: utils.SuccessStr}) }) return initErr diff --git a/internal/handler/handler.go b/internal/handler/handler.go index d0602150..fff3c025 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -75,7 +75,7 @@ func MainHandler(w http.ResponseWriter, r *http.Request) { } if config.PrintAllEvents { - utils.PrintLog("info", log) + utils.PrintLog(utils.InfoStr, log) } metrics.IncreaseCounter(log) diff --git a/internal/kubernetes/client/client.go b/internal/kubernetes/client/client.go index 47af5b50..2ab5fbbe 100644 --- a/internal/kubernetes/client/client.go +++ b/internal/kubernetes/client/client.go @@ -8,11 +8,14 @@ import ( "flag" "fmt" "os" + "strconv" "strings" "sync" "time" + "github.com/google/uuid" appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -28,9 +31,8 @@ import ( "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/remotecommand" toolsWatch "k8s.io/client-go/tools/watch" - "k8s.io/kubectl/pkg/scheme" - klog "k8s.io/klog/v2" + "k8s.io/kubectl/pkg/scheme" "github.com/falcosecurity/falco-talon/configuration" "github.com/falcosecurity/falco-talon/utils" @@ -70,16 +72,8 @@ type KubernetesClient interface { GetLeaseHolder() (<-chan string, error) Exec(namespace, pod, container string, command []string, script string) (*bytes.Buffer, error) CreateEphemeralContainer(pod *corev1.Pod, container, name string, ttl int) error - ListPods(ctx context.Context, opts metav1.ListOptions) (*corev1.PodList, error) - EvictPod(pod corev1.Pod) error -} - -type DrainClient interface { - GetPod(name, namespace string) (*corev1.Pod, error) - GetNodeFromPod(pod *corev1.Pod) (*corev1.Node, error) - ListPods(ctx context.Context, options metav1.ListOptions) (*corev1.PodList, error) + ListPods(opts metav1.ListOptions) (*corev1.PodList, error) EvictPod(pod corev1.Pod) error - GetReplicaSet(name, namespace string) (*appsv1.ReplicaSet, error) } var ( @@ -129,7 +123,7 @@ func Init() error { flag.Parse() if initErr == nil { - utils.PrintLog("info", utils.LogLine{Message: "init", Category: "kubernetes", Status: utils.SuccessStr}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Message: "init", Category: "kubernetes", Status: utils.SuccessStr}) } }) @@ -153,6 +147,14 @@ func (client Client) GetPod(pod, namespace string) (*corev1.Pod, error) { return p, nil } +func (client Client) GetJob(job, namespace string) (*batchv1.Job, error) { + p, err := client.Clientset.BatchV1().Jobs(namespace).Get(context.Background(), job, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("the job '%v' in the namespace '%v' doesn't exist", job, namespace) + } + return p, nil +} + func (client Client) GetDeployment(name, namespace string) (*appsv1.Deployment, error) { p, err := client.Clientset.AppsV1().Deployments(namespace).Get(context.Background(), name, metav1.GetOptions{}) if err != nil { @@ -461,6 +463,13 @@ func (client Client) CreateEphemeralContainer(pod *corev1.Pod, container, name, Stdin: true, TTY: false, TerminationMessagePolicy: corev1.TerminationMessageReadFile, + SecurityContext: &corev1.SecurityContext{ + // Capabilities: &corev1.Capabilities{ + // Add: []corev1.Capability{"SYS_ADMIN", "NET_ADMIN"}, + // }, + Privileged: utils.Pointer(true), + RunAsUser: utils.Pointer(int64(0)), + }, }, TargetContainerName: container, } @@ -497,7 +506,7 @@ func (client Client) CreateEphemeralContainer(pod *corev1.Pod, container, name, return err } - timeout := time.NewTimer(10 * time.Second) + timeout := time.NewTimer(20 * time.Second) ticker := time.NewTicker(300 * time.Millisecond) defer timeout.Stop() defer ticker.Stop() @@ -523,8 +532,8 @@ func (client Client) CreateEphemeralContainer(pod *corev1.Pod, container, name, return nil } -func (client Client) ListPods(ctx context.Context, opts metav1.ListOptions) (*corev1.PodList, error) { - return client.CoreV1().Pods("").List(ctx, opts) +func (client Client) ListPods(opts metav1.ListOptions) (*corev1.PodList, error) { + return client.CoreV1().Pods("").List(context.Background(), opts) } func (client Client) EvictPod(pod corev1.Pod) error { @@ -572,3 +581,111 @@ func GetContainers(pod *corev1.Pod) []string { } return c } + +func (client Client) CreateJob(jobName, namespace, image, node string, ttl int) (string, error) { + execAction := new(corev1.ExecAction) + execAction.Command = []string{"true"} + probe := new(corev1.Probe) + probe.ProbeHandler.Exec = execAction + probe.InitialDelaySeconds = int32(0) + + endAt := time.Now().Local().Add(time.Duration(int64(ttl)) * time.Second) + + uuid := uuid.NewString()[:5] + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: jobName + "-" + uuid, + Annotations: map[string]string{ + "end-at": endAt.Format("2006-01-02 15:04:05"), + }, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": utils.FalcoTalonStr, + "app.kubernetes.io/name": jobName, + "app.kubernetes.io/part-of": utils.FalcoTalonStr, + }, + }, + Spec: batchv1.JobSpec{ + // ActiveDeadlineSeconds: utils.Pointer(int64(ttl)), + TTLSecondsAfterFinished: utils.Pointer(int32(3600)), + BackoffLimit: utils.Pointer(int32(1)), + Completions: utils.Pointer(int32(1)), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: jobName + "-" + uuid, + Annotations: map[string]string{ + "end-at": endAt.Format("2006-01-02 15:04:05"), + }, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": utils.FalcoTalonStr, + "app.kubernetes.io/name": jobName, + "app.kubernetes.io/part-of": utils.FalcoTalonStr, + }, + }, + Spec: corev1.PodSpec{ + NodeName: node, + Containers: []corev1.Container{ + { + Name: jobName, + Command: []string{"sleep", strconv.Itoa(ttl)}, + Ports: []corev1.ContainerPort{}, + LivenessProbe: probe, + ReadinessProbe: probe, + Stdin: true, + TTY: true, + Image: image, + SecurityContext: &corev1.SecurityContext{ + Privileged: utils.Pointer(true), + RunAsUser: utils.Pointer(int64(0)), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "host-fs", + MountPath: "/host", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "host-fs", + VolumeSource: corev1.VolumeSource{ + HostPath: utils.Pointer(corev1.HostPathVolumeSource{Path: "/"})}, + }, + }, + ActiveDeadlineSeconds: utils.Pointer(int64(ttl + 5)), + RestartPolicy: corev1.RestartPolicyNever, + TerminationGracePeriodSeconds: utils.Pointer(int64(1)), + }, + }, + }, + } + + result, err := client.Clientset.BatchV1().Jobs(namespace).Create(context.Background(), job, metav1.CreateOptions{}) + if err != nil { + return "", err + } + + timeout := time.NewTimer(time.Duration(ttl) * time.Second) + ticker := time.NewTicker(300 * time.Millisecond) + defer timeout.Stop() + defer ticker.Stop() + + var ready bool + for !ready { + select { + case <-timeout.C: + return "", fmt.Errorf("the job '%v' in the namespace '%v' is not ready", result.Name, namespace) + case <-ticker.C: + j, err := client.GetJob(result.Name, namespace) + if err != nil { + return "", err + } + if j.Status.Active > 0 { + ready = true + } + } + } + + return result.Name, nil +} diff --git a/internal/minio/client/client.go b/internal/minio/client/client.go index 60fb3f95..2366fbe7 100644 --- a/internal/minio/client/client.go +++ b/internal/minio/client/client.go @@ -43,7 +43,7 @@ func Init() error { } if initErr == nil { - utils.PrintLog("info", utils.LogLine{Message: "init", Category: "minio", Status: utils.SuccessStr}) + utils.PrintLog(utils.InfoStr, utils.LogLine{Message: "init", Category: "minio", Status: utils.SuccessStr}) } }) diff --git a/internal/otlp/metrics/metrics.go b/internal/otlp/metrics/metrics.go index a3f036b8..d48901fb 100644 --- a/internal/otlp/metrics/metrics.go +++ b/internal/otlp/metrics/metrics.go @@ -38,7 +38,7 @@ func Init() { exporter, err := prometheus.New() if err != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err.Error(), Message: "init", Category: "otlp"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err.Error(), Message: "init", Category: "otlp"}) log.Fatal(err) } @@ -53,7 +53,7 @@ func Init() { if config.Otel.MetricsEnabled { otlpExporter, err2 := newOtlpMetricExporter(config) if err2 != nil { - utils.PrintLog("fatal", utils.LogLine{Error: err2.Error(), Message: "init", Category: "otlp"}) + utils.PrintLog(utils.FatalStr, utils.LogLine{Error: err2.Error(), Message: "init", Category: "otlp"}) log.Fatal(err2) } metricOpts = append(metricOpts, sdk.WithReader(sdk.NewPeriodicReader(otlpExporter))) diff --git a/internal/otlp/traces/traces.go b/internal/otlp/traces/traces.go index 6654ae5a..b2c2883c 100644 --- a/internal/otlp/traces/traces.go +++ b/internal/otlp/traces/traces.go @@ -50,7 +50,7 @@ func SetupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, oe shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) otel.SetTracerProvider(tracerProvider) otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Message: "otel"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Message: "otel"}) })) tracer = tracerProvider.Tracer("falco-talon") diff --git a/internal/rules/priority.go b/internal/rules/priority.go index 1d01a75d..223ff892 100644 --- a/internal/rules/priority.go +++ b/internal/rules/priority.go @@ -1,6 +1,10 @@ package rules -import "strings" +import ( + "strings" + + "github.com/falcosecurity/falco-talon/utils" +) const ( Default = iota @@ -22,7 +26,7 @@ func getPriorityNumber(priority string) int { return Alert case "critical": return Critical - case "error": + case utils.ErrorStr: return Error case "warning": return Warning diff --git a/internal/rules/rules.go b/internal/rules/rules.go index 99aff5e2..b78df277 100644 --- a/internal/rules/rules.go +++ b/internal/rules/rules.go @@ -88,7 +88,7 @@ func init() { func ParseRules(files []string) *[]*Rule { a, r, err := extractActionsRules(files) if err != nil { - utils.PrintLog("error", utils.LogLine{Error: err.Error(), Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: err.Error(), Message: "rules"}) return nil } @@ -122,7 +122,7 @@ func ParseRules(files []string) *[]*Rule { continue } if rule.Actions[n].Parameters[k] != nil && ru.Kind() != rt.Kind() { - utils.PrintLog("error", utils.LogLine{Error: "mismatch of type for a parameter", Message: "rules", Rule: rule.GetName(), Action: action.GetName()}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "mismatch of type for a parameter", Message: "rules", Rule: rule.GetName(), Action: action.GetName()}) continue } switch rt.Kind() { @@ -157,7 +157,7 @@ func ParseRules(files []string) *[]*Rule { continue } if rule.Actions[n].Output.Parameters[k] != nil && ru.Kind() != rt.Kind() { - utils.PrintLog("error", utils.LogLine{Error: "mismatch of type for a parameter", Message: "rules", Rule: rule.GetName(), Action: action.GetName(), OutputTarget: action.Output.GetTarget()}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "mismatch of type for a parameter", Message: "rules", Rule: rule.GetName(), Action: action.GetName(), OutputTarget: action.Output.GetTarget()}) continue } switch rt.Kind() { @@ -390,57 +390,57 @@ func extractActionsRules(files []string) (*[]*Action, *[]*Rule, error) { func (rule *Rule) isValid() bool { valid := true if rule.Name == "" { - utils.PrintLog("error", utils.LogLine{Error: "all rules must have a name", Message: "rules"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "all rules must have a name", Message: "rules"}) valid = false } if rule.Continue != "" && rule.Continue != trueStr && rule.Continue != falseStr { - utils.PrintLog("error", utils.LogLine{Error: "'continue' setting can be 'true' or 'false' only", Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "'continue' setting can be 'true' or 'false' only", Message: "rules", Rule: rule.Name}) valid = false } if rule.DryRun != "" && rule.DryRun != trueStr && rule.DryRun != falseStr { - utils.PrintLog("error", utils.LogLine{Error: "'dry_run' setting can be 'true' or 'false' only", Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "'dry_run' setting can be 'true' or 'false' only", Message: "rules", Rule: rule.Name}) valid = false } if len(rule.Actions) == 0 { - utils.PrintLog("error", utils.LogLine{Error: "no action specified", Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "no action specified", Message: "rules", Rule: rule.Name}) valid = false } if len(rule.Actions) != 0 { for _, i := range rule.Actions { if i.Name == "" { - utils.PrintLog("error", utils.LogLine{Error: "action without a name", Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "action without a name", Message: "rules", Rule: rule.Name}) valid = false } if i.Actionner == "" { - utils.PrintLog("error", utils.LogLine{Error: "missing actionner", Message: "rules", Action: i.Name, Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "missing actionner", Message: "rules", Action: i.Name, Rule: rule.Name}) valid = false } if !actionCheckRegex.MatchString(i.Actionner) { - utils.PrintLog("error", utils.LogLine{Error: "incorrect actionner", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "incorrect actionner", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name}) valid = false } if i.Continue != "" && i.Continue != trueStr && i.Continue != falseStr { - utils.PrintLog("error", utils.LogLine{Error: "'continue' setting can be 'true' or 'false' only", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "'continue' setting can be 'true' or 'false' only", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name}) valid = false } if i.IgnoreErrors != "" && i.IgnoreErrors != trueStr && i.IgnoreErrors != falseStr { - utils.PrintLog("error", utils.LogLine{Error: "'ignore_errors' setting can be 'true' or 'false' only", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "'ignore_errors' setting can be 'true' or 'false' only", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name}) valid = false } if i.Output.Target != "" && len(i.Output.Parameters) == 0 { - utils.PrintLog("error", utils.LogLine{Error: "missing 'parameters' for the output", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name, OutputTarget: i.Output.Target}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "missing 'parameters' for the output", Message: "rules", Action: i.Name, Actionner: i.Actionner, Rule: rule.Name, OutputTarget: i.Output.Target}) valid = false } } } if !priorityCheckRegex.MatchString(rule.Match.Priority) { - utils.PrintLog("error", utils.LogLine{Error: fmt.Sprintf("incorrect priority '%v'", rule.Match.Priority), Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: fmt.Sprintf("incorrect priority '%v'", rule.Match.Priority), Message: "rules", Rule: rule.Name}) valid = false } for _, i := range rule.Match.TagsC { for _, j := range i { if !tagCheckRegex.MatchString(j) { - utils.PrintLog("error", utils.LogLine{Error: fmt.Sprintf("incorrect tag '%v'", j), Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: fmt.Sprintf("incorrect tag '%v'", j), Message: "rules", Rule: rule.Name}) valid = false } } @@ -449,13 +449,13 @@ func (rule *Rule) isValid() bool { t := strings.Split(strings.ReplaceAll(i, ", ", ","), ",") for _, j := range t { if !outputFieldKeyCheckRegex.MatchString(j) { - utils.PrintLog("error", utils.LogLine{Error: fmt.Sprintf("incorrect output field key '%v'", j), Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: fmt.Sprintf("incorrect output field key '%v'", j), Message: "rules", Rule: rule.Name}) valid = false } } } if err := rule.setPriorityNumberComparator(); err != nil { - utils.PrintLog("error", utils.LogLine{Error: fmt.Sprintf("incorrect priority comparator '%v'", rule.Match.PriorityComparator), Message: "rules", Rule: rule.Name}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: fmt.Sprintf("incorrect priority comparator '%v'", rule.Match.PriorityComparator), Message: "rules", Rule: rule.Name}) valid = false } return valid diff --git a/notifiers/elasticsearch/mapping.go b/notifiers/elasticsearch/mapping.go index 0eff5838..e7cf0f34 100644 --- a/notifiers/elasticsearch/mapping.go +++ b/notifiers/elasticsearch/mapping.go @@ -31,7 +31,7 @@ var mapping = ` } } }, - "error": { + utils.ErrorStr: { "type": "text", "fields": { "keyword": { diff --git a/notifiers/http/client.go b/notifiers/http/client.go index cba15e92..92377d66 100644 --- a/notifiers/http/client.go +++ b/notifiers/http/client.go @@ -113,7 +113,7 @@ func (c *Client) Request(u string, payload any) error { // defer + recover to catch panic if output doesn't respond defer func() { if err := recover(); err != nil { - utils.PrintLog("error", utils.LogLine{Error: "recover"}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Error: "recover"}) } }() diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index 460e9dc0..9f4dc294 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -99,7 +99,7 @@ func Init() { for _, j := range *defaultNotifiers { if strings.ToLower(i) == j.Information().Name { if err := j.Init(config.Notifiers[i]); err != nil { - utils.PrintLog("error", utils.LogLine{Message: "init", Error: err.Error(), Category: j.Information().Name, Status: utils.FailureStr}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Message: "init", Error: err.Error(), Category: j.Information().Name, Status: utils.FailureStr}) continue } enabledNotifiers.Add(j) @@ -157,12 +157,12 @@ func Notify(actx context.Context, rule *rules.Rule, action *rules.Action, event span.RecordError(err) logN.Status = utils.FailureStr logN.Error = err.Error() - utils.PrintLog("error", logN) + utils.PrintLog(utils.ErrorStr, logN) metrics.IncreaseCounter(log) } else { span.SetStatus(codes.Ok, "notification successfully sent") logN.Status = utils.SuccessStr - utils.PrintLog("info", logN) + utils.PrintLog(utils.InfoStr, logN) metrics.IncreaseCounter(logN) } span.End() diff --git a/outputs/outputs.go b/outputs/outputs.go index d070e2d9..be7e4455 100644 --- a/outputs/outputs.go +++ b/outputs/outputs.go @@ -91,7 +91,7 @@ func Init() error { for _, output := range *defaultOutputs { if category == output.Information().Category { if err := output.Init(); err != nil { - utils.PrintLog("error", utils.LogLine{Message: "init", Error: err.Error(), Category: category, Status: utils.FailureStr}) + utils.PrintLog(utils.ErrorStr, utils.LogLine{Message: "init", Error: err.Error(), Category: category, Status: utils.FailureStr}) return err } enabledCategories[category] = true diff --git a/rules.yaml b/rules.yaml index dd962063..4823e469 100644 --- a/rules.yaml +++ b/rules.yaml @@ -196,6 +196,22 @@ prefix: /tcpdump/ region: us-east-1 +- rule: Test sysdig + match: + rules: + - Test sysdig + actions: + - action: Test sysdig + actionner: kubernetes:sysdig + parameters: + duration: 5 + scope: pod + buffer_size: 4096 + output: + target: local:file + parameters: + destination: /tmp/ + - rule: Test log match: rules: diff --git a/utils/utils.go b/utils/utils.go index 184ff789..948bae33 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -30,15 +30,17 @@ const ( MapIntStr string = "map[string]int" MapInterfaceStr string = "map[string]interface {}" - errorStr string = "error" - warningStr string = "warning" - fatalStr string = "fatal" + InfoStr string = "info" + ErrorStr string = "error" + WarningStr string = "warning" + FatalStr string = "fatal" textStr string = "text" colorStr string = "color" - SuccessStr string = "success" - FailureStr string = "failure" + InProgressStr string = "in_progress" + SuccessStr string = "success" + FailureStr string = "failure" ansiChars string = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" @@ -106,11 +108,11 @@ func PrintLog(level string, line LogLine) { var l *zerolog.Event switch strings.ToLower(level) { - case warningStr: + case WarningStr: l = log.Warn() - case errorStr: + case ErrorStr: l = log.Error() - case fatalStr: + case FatalStr: l = log.Fatal() default: l = log.Info() @@ -304,3 +306,7 @@ func GetLocalIP() *string { } return localIP } + +func Pointer[T any](d T) *T { + return &d +}