diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..cd425c4 --- /dev/null +++ b/db/db.go @@ -0,0 +1,73 @@ +package db + +import ( + "database/sql" + "log" +) + +const queryBadIpsPerJail = "SELECT j.name, (SELECT COUNT(1) FROM bips b WHERE j.name = b.jail) FROM jails j" +const queryBannedIpsPerJail = "SELECT j.name, (SELECT COUNT(1) FROM bans b WHERE j.name = b.jail) FROM jails j" + +type Fail2BanDB struct { + DatabasePath string + sqliteDB *sql.DB +} + +func MustConnectToDb(databasePath string) *Fail2BanDB { + db, err := sql.Open("sqlite3", databasePath) + if err != nil { + log.Fatal(err) + } + return &Fail2BanDB{ + DatabasePath: databasePath, + sqliteDB: db, + } +} + +func (db *Fail2BanDB) CountBannedIpsPerJail() (map[string]int, error) { + return db.RunJailNameToCountQuery(queryBannedIpsPerJail) +} + +func (db *Fail2BanDB) CountBadIpsPerJail() (map[string]int, error) { + return db.RunJailNameToCountQuery(queryBadIpsPerJail) +} + +func (db *Fail2BanDB) RunJailNameToCountQuery(query string) (map[string]int, error) { + stmt, err := db.sqliteDB.Prepare(query) + defer db.mustCloseStatement(stmt) + + if err != nil { + return nil, err + } + + jailNameToCountMap := map[string]int{} + rows, err := stmt.Query() + if err != nil { + return nil, err + } + if rows == nil { + return jailNameToCountMap, nil + } + + for rows.Next() { + if rows.Err() != nil { + return nil, err + } + jailName := "" + count := 0 + err = rows.Scan(&jailName, &count) + if err != nil { + return nil, err + } + + jailNameToCountMap[jailName] = count + } + return jailNameToCountMap, nil +} + +func (db *Fail2BanDB) mustCloseStatement(stmt *sql.Stmt) { + err := stmt.Close() + if err != nil { + log.Fatal(err) + } +} diff --git a/exporter.go b/exporter.go index 19e3d2e..19b2d7d 100644 --- a/exporter.go +++ b/exporter.go @@ -1,6 +1,8 @@ package main import ( + fail2banDb "fail2ban-prometheus-exporter/db" + _ "github.com/mattn/go-sqlite3" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "log" @@ -9,23 +11,66 @@ import ( const namespace = "fail2ban" -var up = prometheus.NewDesc( - prometheus.BuildFQName(namespace, "", "up"), - "Was the last fail2ban query successful.", - nil, nil, +var ( + db = fail2banDb.MustConnectToDb("fail2ban.sqlite3") + metricUp = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "", "up"), + "Was the last fail2ban query successful.", + nil, nil, + ) + metricBannedIpsPerJail = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "", "banned_ips"), + "Number of banned IPs stored in the database (per jail).", + []string{"jail"}, nil, + ) + metricBadIpsPerJail = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "", "bad_ips"), + "Number of bad IPs stored in the database (per jail).", + []string{"jail"}, nil, + ) ) type Exporter struct { } func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { - ch <- up + ch <- metricUp + ch <- metricBadIpsPerJail + ch <- metricBannedIpsPerJail } func (e *Exporter) Collect(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric( - up, prometheus.GaugeValue, 1, + metricUp, prometheus.GaugeValue, 1, ) + collectBadIpsPerJailMetrics(ch) + collectBannedIpsPerJailMetrics(ch) +} + +func collectBadIpsPerJailMetrics(ch chan<- prometheus.Metric) { + jailNameToCountMap, err := db.CountBadIpsPerJail() + if err != nil { + log.Print(err) + } + + for jailName, count := range jailNameToCountMap { + ch <- prometheus.MustNewConstMetric( + metricBadIpsPerJail, prometheus.GaugeValue, float64(count), jailName, + ) + } +} + +func collectBannedIpsPerJailMetrics(ch chan<- prometheus.Metric) { + jailNameToCountMap, err := db.CountBannedIpsPerJail() + if err != nil { + log.Print(err) + } + + for jailName, count := range jailNameToCountMap { + ch <- prometheus.MustNewConstMetric( + metricBannedIpsPerJail, prometheus.GaugeValue, float64(count), jailName, + ) + } } func main() { diff --git a/go.mod b/go.mod index 63dc5e1..2df78ea 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module fail2ban-prometheus-exporter go 1.15 -require github.com/prometheus/client_golang v1.9.0 +require ( + github.com/mattn/go-sqlite3 v1.14.6 + github.com/prometheus/client_golang v1.9.0 +) diff --git a/go.sum b/go.sum index 94b6a13..7de5c32 100644 --- a/go.sum +++ b/go.sum @@ -150,6 +150,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=