From bd6566eea853c1203ccb04cee1b3b5040d5ace36 Mon Sep 17 00:00:00 2001 From: Hector <dev@hsmith.org> Date: Sun, 29 Aug 2021 17:05:48 +0100 Subject: [PATCH 1/3] new status command Add support for running the `status` command and processing the server response data. Add new metric for the number of jails. --- src/exporter.go | 18 ++++++++++++++++++ src/socket/fail2banSocket.go | 35 +++++++++++++++++++++++++++++++++-- src/socket/protocol.go | 1 + 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/exporter.go b/src/exporter.go index 5d568c3..6dd422f 100644 --- a/src/exporter.go +++ b/src/exporter.go @@ -54,6 +54,11 @@ var ( "Check if the fail2ban server is up", nil, nil, ) + metricJailCount = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_count"), + "Number of defined jails", + nil, nil, + ) ) type Exporter struct { @@ -73,6 +78,7 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { } if e.socket != nil { ch <- metricServerPing + ch <- metricJailCount } } @@ -86,6 +92,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { } if e.socket != nil { e.collectServerPingMetric(ch) + e.collectJailCountMetric(ch) } } @@ -164,6 +171,17 @@ func (e *Exporter) collectServerPingMetric(ch chan<- prometheus.Metric) { ) } +func (e *Exporter) collectJailCountMetric(ch chan<- prometheus.Metric) { + jails, err := e.socket.GetJails() + var count float64 = 0 + if err == nil { + count = float64(len(jails)) + } + ch <- prometheus.MustNewConstMetric( + metricJailCount, prometheus.GaugeValue, count, + ) +} + func printAppVersion() { fmt.Println(version) fmt.Printf(" build date: %s\r\n commit hash: %s\r\n built by: %s\r\n", date, commit, builtBy) diff --git a/src/socket/fail2banSocket.go b/src/socket/fail2banSocket.go index 35513c8..48e2ad6 100644 --- a/src/socket/fail2banSocket.go +++ b/src/socket/fail2banSocket.go @@ -3,7 +3,8 @@ package socket import ( "log" "net" - + "fmt" + "strings" "github.com/kisielk/og-rek" "github.com/nlpodyssey/gopickle/types" ) @@ -37,6 +38,36 @@ func (s *Fail2BanSocket) Ping() bool { } log.Printf("unexpected response data: %s", t) } - log.Printf("unexpected response format - cannot parse: %v", response) + log.Printf("(%s) unexpected response format - cannot parse: %v", pingCommand, response) return false } + +func (s *Fail2BanSocket) GetJails() ([]string, error) { + response, err := s.sendCommand([]string{statusCommand}) + if err != nil { + return nil, err + } + + if lvl1, ok := response.(*types.Tuple); ok { + if lvl2, ok := lvl1.Get(1).(*types.List); ok { + if lvl3, ok := lvl2.Get(1).(*types.Tuple); ok { + if lvl4, ok := lvl3.Get(1).(string); ok { + splitJails := strings.Split(lvl4, ",") + return trimSpaceForAll(splitJails), nil + } + } + } + } + return nil, newBadFormatError(statusCommand, response) +} + +func newBadFormatError(command string, data interface{}) error { + return fmt.Errorf("(%s) unexpected response format - cannot parse: %v", command, data) +} + +func trimSpaceForAll(slice []string) []string { + for i := range slice { + slice[i] = strings.TrimSpace(slice[i]) + } + return slice +} diff --git a/src/socket/protocol.go b/src/socket/protocol.go index 1cf533d..5320f5b 100644 --- a/src/socket/protocol.go +++ b/src/socket/protocol.go @@ -10,6 +10,7 @@ import ( const ( commandTerminator = "<F2B_END_COMMAND>" pingCommand = "ping" + statusCommand = "status" socketReadBufferSize = 1024 ) From 3dd68cd8e6372b4b6c919dbfe9320e9b5ac93876 Mon Sep 17 00:00:00 2001 From: Hector <dev@hsmith.org> Date: Sun, 29 Aug 2021 17:33:12 +0100 Subject: [PATCH 2/3] collect metrics on jail stats Collect more stats on each jail (number of current/total failures, and number of current/total bans). This is the same data shown by the client when running the `status` command. Add new metrics to export the new data. --- src/exporter.go | 54 ++++++++++++++++++++++++++++++++-- src/socket/fail2banSocket.go | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/exporter.go b/src/exporter.go index 6dd422f..146eab4 100644 --- a/src/exporter.go +++ b/src/exporter.go @@ -49,6 +49,7 @@ var ( "Number of errors found since startup.", []string{"type"}, nil, ) + metricServerPing = prometheus.NewDesc( prometheus.BuildFQName(sockNamespace, "", "up"), "Check if the fail2ban server is up", @@ -59,6 +60,26 @@ var ( "Number of defined jails", nil, nil, ) + metricJailFailedCurrent = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_failed_current"), + "Number of current failures on this jail's filter", + []string{"jail"}, nil, + ) + metricJailFailedTotal = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_failed_total"), + "Number of total failures on this jail's filter", + []string{"jail"}, nil, + ) + metricJailBannedCurrent = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_banned_current"), + "Number of IPs currently banned in this jail", + []string{"jail"}, nil, + ) + metricJailBannedTotal = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_banned_total"), + "Total number of IPs banned by this jail (includes expired bans)", + []string{"jail"}, nil, + ) ) type Exporter struct { @@ -79,6 +100,10 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { if e.socket != nil { ch <- metricServerPing ch <- metricJailCount + ch <- metricJailFailedCurrent + ch <- metricJailFailedTotal + ch <- metricJailBannedCurrent + ch <- metricJailBannedTotal } } @@ -92,7 +117,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { } if e.socket != nil { e.collectServerPingMetric(ch) - e.collectJailCountMetric(ch) + e.collectJailMetrics(ch) } } @@ -171,7 +196,7 @@ func (e *Exporter) collectServerPingMetric(ch chan<- prometheus.Metric) { ) } -func (e *Exporter) collectJailCountMetric(ch chan<- prometheus.Metric) { +func (e *Exporter) collectJailMetrics(ch chan<- prometheus.Metric) { jails, err := e.socket.GetJails() var count float64 = 0 if err == nil { @@ -180,6 +205,31 @@ func (e *Exporter) collectJailCountMetric(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric( metricJailCount, prometheus.GaugeValue, count, ) + + for i := range(jails) { + e.collectJailStatsMetric(ch, jails[i]) + } +} + +func (e *Exporter) collectJailStatsMetric(ch chan<- prometheus.Metric, jail string) { + stats, err := e.socket.GetJailStats(jail) + if err != nil { + log.Printf("failed to get stats for jail %s: %v", jail, err) + return + } + + ch <- prometheus.MustNewConstMetric( + metricJailFailedCurrent, prometheus.GaugeValue, float64(stats.FailedCurrent), jail, + ) + ch <- prometheus.MustNewConstMetric( + metricJailFailedTotal, prometheus.GaugeValue, float64(stats.FailedTotal), jail, + ) + ch <- prometheus.MustNewConstMetric( + metricJailBannedCurrent, prometheus.GaugeValue, float64(stats.BannedCurrent), jail, + ) + ch <- prometheus.MustNewConstMetric( + metricJailBannedTotal, prometheus.GaugeValue, float64(stats.BannedTotal), jail, + ) } func printAppVersion() { diff --git a/src/socket/fail2banSocket.go b/src/socket/fail2banSocket.go index 48e2ad6..61d273d 100644 --- a/src/socket/fail2banSocket.go +++ b/src/socket/fail2banSocket.go @@ -14,6 +14,13 @@ type Fail2BanSocket struct { encoder *ogórek.Encoder } +type JailStats struct { + FailedCurrent int + FailedTotal int + BannedCurrent int + BannedTotal int +} + func MustConnectToSocket(path string) *Fail2BanSocket { c, err := net.Dial("unix", path) if err != nil { @@ -61,6 +68,55 @@ func (s *Fail2BanSocket) GetJails() ([]string, error) { return nil, newBadFormatError(statusCommand, response) } +func (s *Fail2BanSocket) GetJailStats(jail string) (JailStats, error) { + response, err := s.sendCommand([]string{statusCommand, jail}) + if err != nil { + return JailStats{}, err + } + + stats := JailStats{ + FailedCurrent: -1, + FailedTotal: -1, + BannedCurrent: -1, + BannedTotal: -1, + } + + if lvl1, ok := response.(*types.Tuple); ok { + if lvl2, ok := lvl1.Get(1).(*types.List); ok { + if filter, ok := lvl2.Get(0).(*types.Tuple); ok { + if filterLvl1, ok := filter.Get(1).(*types.List); ok { + if filterCurrentTuple, ok := filterLvl1.Get(0).(*types.Tuple); ok { + if filterCurrent, ok := filterCurrentTuple.Get(1).(int); ok { + stats.FailedCurrent = filterCurrent + } + } + if filterTotalTuple, ok := filterLvl1.Get(1).(*types.Tuple); ok { + if filterTotal, ok := filterTotalTuple.Get(1).(int); ok { + stats.FailedTotal = filterTotal + } + } + } + } + if actions, ok := lvl2.Get(1).(*types.Tuple); ok { + if actionsLvl1, ok := actions.Get(1).(*types.List); ok { + if actionsCurrentTuple, ok := actionsLvl1.Get(0).(*types.Tuple); ok { + if actionsCurrent, ok := actionsCurrentTuple.Get(1).(int); ok { + stats.BannedCurrent = actionsCurrent + } + } + if actionsTotalTuple, ok := actionsLvl1.Get(1).(*types.Tuple); ok { + if actionsTotal, ok := actionsTotalTuple.Get(1).(int); ok { + stats.BannedTotal = actionsTotal + } + } + } + } + return stats, nil + } + } + return stats, newBadFormatError(statusCommand, response) +} + func newBadFormatError(command string, data interface{}) error { return fmt.Errorf("(%s) unexpected response format - cannot parse: %v", command, data) } From efc789cec2ffe30545644c19f7c7a13b8c7d0dc7 Mon Sep 17 00:00:00 2001 From: Hector <dev@hsmith.org> Date: Sun, 29 Aug 2021 17:33:48 +0100 Subject: [PATCH 3/3] go fmt --- src/exporter.go | 4 ++-- src/socket/fail2banSocket.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/exporter.go b/src/exporter.go index 146eab4..97e8887 100644 --- a/src/exporter.go +++ b/src/exporter.go @@ -49,7 +49,7 @@ var ( "Number of errors found since startup.", []string{"type"}, nil, ) - + metricServerPing = prometheus.NewDesc( prometheus.BuildFQName(sockNamespace, "", "up"), "Check if the fail2ban server is up", @@ -206,7 +206,7 @@ func (e *Exporter) collectJailMetrics(ch chan<- prometheus.Metric) { metricJailCount, prometheus.GaugeValue, count, ) - for i := range(jails) { + for i := range jails { e.collectJailStatsMetric(ch, jails[i]) } } diff --git a/src/socket/fail2banSocket.go b/src/socket/fail2banSocket.go index 61d273d..8cce14f 100644 --- a/src/socket/fail2banSocket.go +++ b/src/socket/fail2banSocket.go @@ -1,12 +1,12 @@ package socket import ( - "log" - "net" "fmt" - "strings" "github.com/kisielk/og-rek" "github.com/nlpodyssey/gopickle/types" + "log" + "net" + "strings" ) type Fail2BanSocket struct { @@ -76,9 +76,9 @@ func (s *Fail2BanSocket) GetJailStats(jail string) (JailStats, error) { stats := JailStats{ FailedCurrent: -1, - FailedTotal: -1, + FailedTotal: -1, BannedCurrent: -1, - BannedTotal: -1, + BannedTotal: -1, } if lvl1, ok := response.(*types.Tuple); ok {