Skip to content

Commit

Permalink
Feature/syslog exporter (#92)
Browse files Browse the repository at this point in the history
* Adding base syslog exporter - doesn't work yet

Signed-off-by: Amit Schendel <[email protected]>

* Adding fixes

Signed-off-by: Amit Schendel <[email protected]>

* Adding better logs

Signed-off-by: Amit Schendel <[email protected]>

* Changing default value of protocol

Signed-off-by: Amit Schendel <[email protected]>

* Fixing helm chart

Signed-off-by: Amit Schendel <[email protected]>

---------

Signed-off-by: Amit Schendel <[email protected]>
Co-authored-by: Ben Hirschberg <[email protected]>
  • Loading branch information
amitschendel and slashben authored Dec 14, 2023
1 parent 85f4dce commit 5238472
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 2 deletions.
6 changes: 6 additions & 0 deletions chart/kubecop/templates/deamonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ spec:
- name: ALERTMANAGER_URL
value: {{ .Values.kubecop.alertmanager.endpoint }}
{{- end }}
{{- if .Values.kubecop.syslog.enabled }}
- name: SYSLOG_HOST
value: {{ .Values.kubecop.syslog.endpoint }}
- name: SYSLOG_PROTOCOL
value: {{ .Values.kubecop.syslog.protocol }}
{{- end }}
{{- if .Values.kubecop.pprofserver.enabled }}
- name: _PPROF_SERVER
value: "true"
Expand Down
4 changes: 4 additions & 0 deletions chart/kubecop/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ kubecop:
alertmanager:
enabled: false
endpoint: "localhost:9093"
syslog:
enabled: false
endpoint: "localhost:514"
protocol: "udp"
prometheusExporter:
enabled: false
pprofserver:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ require (
github.com/containerd/ttrpc v1.2.2 // indirect
github.com/containerd/typeurl/v2 v2.1.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/crewjam/rfc5424 v0.1.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
Expand Down Expand Up @@ -123,6 +124,7 @@ require (
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/cri-api v0.28.4 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/crewjam/rfc5424 v0.1.0 h1:MSeXJm22oKovLzWj44AHwaItjIMUMugYGkEzfa831H8=
github.com/crewjam/rfc5424 v0.1.0/go.mod h1:RCi9M3xHVOeerf6ULZzqv2xOGRO/zYaVUeRyPnBW3gQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand Down Expand Up @@ -524,6 +526,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/mcuadros/go-syslog.v2 v2.3.0 h1:kcsiS+WsTKyIEPABJBJtoG0KkOS6yzvJ+/eZlhD79kk=
gopkg.in/mcuadros/go-syslog.v2 v2.3.0/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
4 changes: 3 additions & 1 deletion pkg/engine/rule/r1001_exec_binary_not_in_base_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ func findProcessByMountNamespace(execEvent *tracing.ExecveEvent) (*procfs.Proc,
// Check if the mount namespace ID matches the specified namespaceID
mountNamespaceId, err := getMountNamespaceID(proc.PID)
if err != nil {
log.Printf("Error reading mount namespace ID for PID %d: %s\n", proc.PID, err)
if os.Getenv("DEBUG") == "true" {
log.Printf("Error reading mount namespace ID for PID %d: %s\n", proc.PID, err)
}
continue
}

Expand Down
10 changes: 9 additions & 1 deletion pkg/exporters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This package contains the exporters for the KubeCope project.
The following exporters are available:
- [Alertmanager](https://github.com/prometheus/alertmanager)
- STD OUT
- SYSLOG

### Alertmanager
The Alertmanager exporter is used to send alerts to the Alertmanager. The Alertmanager will then send the alerts to the configured receivers.
Expand All @@ -14,4 +15,11 @@ To enable the Alertmanager exporter, set the following environment variables:
### STD OUT
The STD OUT exporter is used to print the alerts to the standard output. This exporter is enabled by default.
To disable the STD OUT exporter, set the following environment variable:
- `STDOUT_ENABLED`: Set to `false` to disable the STD OUT exporter.
- `STDOUT_ENABLED`: Set to `false` to disable the STD OUT exporter.

### SYSLOG
The SYSLOG exporter is used to send the alerts to a syslog server. This exporter is disabled by default.
NOTE: The SYSLOG messages format is RFC 5424.
To enable the SYSLOG exporter, set the following environment variables:
- `SYSLOG_HOST`: The host of the syslog server. Example: `localhost:514`
- `SYSLOG_PROTOCOL`: The protocol of the syslog server. Example: `tcp` or `udp`
6 changes: 6 additions & 0 deletions pkg/exporters/exporters_bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
type ExportersConfig struct {
StdoutExporter *bool `yaml:"stdoutExporter"`
AlertManagerExporterURL string `yaml:"alertManagerExporterURL"`
SyslogExporter string `yaml:"syslogExporterURL"`
}

// this file will contain the single point of contact for all exporters
Expand All @@ -29,6 +30,11 @@ func InitExporters(exportersConfig ExportersConfig) {
if stdoutExp != nil {
exporters = append(exporters, stdoutExp)
}
syslogExp := InitSyslogExporter(exportersConfig.SyslogExporter)
if syslogExp != nil {
exporters = append(exporters, syslogExp)
}

if len(exporters) == 0 {
panic("no exporters were initialized")
}
Expand Down
119 changes: 119 additions & 0 deletions pkg/exporters/syslog_exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package exporters

import (
"fmt"
"log"
"log/syslog"
"os"
"time"

"github.com/crewjam/rfc5424"

"github.com/armosec/kubecop/pkg/engine/rule"
)

// SyslogExporter is an exporter that sends alerts to syslog
type SyslogExporter struct {
writer *syslog.Writer
}

// InitSyslogExporter initializes a new SyslogExporter
func InitSyslogExporter(syslogHost string) *SyslogExporter {
if syslogHost == "" {
syslogHost = os.Getenv("SYSLOG_HOST")
if syslogHost == "" {
return nil
}
}

// Set default protocol to UDP
if os.Getenv("SYSLOG_PROTOCOL") == "" {
os.Setenv("SYSLOG_PROTOCOL", "udp")
}

writer, err := syslog.Dial(os.Getenv("SYSLOG_PROTOCOL"), syslogHost, syslog.LOG_ERR, "kubecop")
if err != nil {
log.Printf("failed to initialize syslog exporter: %v", err)
return nil
}

return &SyslogExporter{
writer: writer,
}
}

// SendAlert sends an alert to syslog (RFC 5424) - https://tools.ietf.org/html/rfc5424
func (se *SyslogExporter) SendAlert(failedRule rule.RuleFailure) {
message := rfc5424.Message{
Priority: rfc5424.Error,
Timestamp: time.Unix(failedRule.Event().Timestamp, 0),
Hostname: failedRule.Event().PodName,
AppName: failedRule.Event().ContainerName,
ProcessID: fmt.Sprintf("%d", failedRule.Event().Pid),
StructuredData: []rfc5424.StructuredData{
{
ID: "kubecop - General Event",
Parameters: []rfc5424.SDParam{
{
Name: "rule",
Value: failedRule.Name(),
},
{
Name: "priority",
Value: fmt.Sprintf("%d", failedRule.Priority()),
},
{
Name: "error",
Value: failedRule.Error(),
},
{
Name: "fix_suggestion",
Value: failedRule.FixSuggestion(),
},
{
Name: "ppid",
Value: fmt.Sprintf("%d", failedRule.Event().Ppid),
},
{
Name: "comm",
Value: failedRule.Event().Comm,
},
{
Name: "uid",
Value: fmt.Sprintf("%d", failedRule.Event().Uid),
},
{
Name: "gid",
Value: fmt.Sprintf("%d", failedRule.Event().Gid),
},
{
Name: "namespace",
Value: failedRule.Event().Namespace,
},
{
Name: "pod_name",
Value: failedRule.Event().PodName,
},
{
Name: "container_name",
Value: failedRule.Event().ContainerName,
},
{
Name: "container_id",
Value: failedRule.Event().ContainerID,
},
{
Name: "cwd",
Value: failedRule.Event().Cwd,
},
},
},
},
Message: []byte(failedRule.Error()),
}

_, err := message.WriteTo(se.writer)
if err != nil {
log.Printf("failed to send alert to syslog: %v", err)
}
}
70 changes: 70 additions & 0 deletions pkg/exporters/syslog_exporter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package exporters

import (
"os"
"testing"
"time"

"github.com/armosec/kubecop/pkg/engine/rule"
"github.com/kubescape/kapprofiler/pkg/tracing"
"github.com/stretchr/testify/assert"
"gopkg.in/mcuadros/go-syslog.v2"
)

func setupServer() *syslog.Server {
channel := make(syslog.LogPartsChannel, 100)
handler := syslog.NewChannelHandler(channel)

server := syslog.NewServer()
server.SetFormat(syslog.Automatic)
server.SetHandler(handler)
server.ListenUDP("0.0.0.0:514")
server.Boot()

go func(channel syslog.LogPartsChannel) {
for logParts := range channel {
// Assert logParts is not nil
if assert.NotNil(nil, logParts) {
// Assert logParts["content"] is not nil
if assert.NotNil(nil, logParts["content"]) {
// Assert logParts["message"].(string) is not empty
assert.NotEmpty(nil, logParts["content"].(string))
}
} else {
os.Exit(1)
}
}
}(channel)

go server.Wait()

return server
}

func TestSyslogExporter(t *testing.T) {
// Set up a mock syslog server
server := setupServer()
defer server.Kill()

// Set up environment variables for the exporter
syslogHost := "localhost:514"
os.Setenv("SYSLOG_HOST", syslogHost)
os.Setenv("SYSLOG_PROTOCOL", "udp")

// Initialize the syslog exporter
syslogExp := InitSyslogExporter("")
if syslogExp == nil {
t.Errorf("Expected syslogExp to not be nil")
}

// Send an alert
syslogExp.SendAlert(&rule.R0001UnexpectedProcessLaunchedFailure{
RuleName: "testrule",
Err: "Application profile is missing",
FailureEvent: &tracing.ExecveEvent{GeneralEvent: tracing.GeneralEvent{
ContainerName: "testcontainer", ContainerID: "testcontainerid", Namespace: "testnamespace", PodName: "testpodname"}},
})

// Allow some time for the message to reach the mock syslog server
time.Sleep(200 * time.Millisecond)
}

0 comments on commit 5238472

Please sign in to comment.