-
Miek Gieben authored
Remove iptables; doesn't add a lot and clashes with nftables, just remove it. Signed-off-by:
Miek Gieben <miek.gieben@ru.nl>
Miek Gieben authoredRemove iptables; doesn't add a lot and clashes with nftables, just remove it. Signed-off-by:
Miek Gieben <miek.gieben@ru.nl>
main.go 4.67 KiB
package main
import (
"bufio"
"bytes"
"context"
"flag"
"os"
"os/exec"
"strconv"
"strings"
"time"
"go.science.ru.nl/log"
"go.science.ru.nl/promfmt"
)
var (
flagWrite = flag.Bool("w", true, "write to /var/lib/prometheus/node-exporter/f2bne.prom")
flagDuration = flag.Uint("t", 60, "default duration to export in seconds")
flagDebug = flag.Bool("d", false, "enable debug logging")
)
const promfile = "/var/lib/prometheus/node-exporter/f2bne.prom"
func main() {
flag.Parse()
if *flagDebug {
log.D.Set()
}
doit()
}
type jail struct {
name string
failedTotal float64
bannedTotal float64
failedNow float64
bannedNow float64
}
func fail2ban(args ...string) ([]byte, error) {
ctx := context.TODO()
cmd := exec.CommandContext(ctx, "fail2ban-client", args...)
log.Debugf("running fail2ban %v", cmd.Args)
out, err := cmd.CombinedOutput()
if len(out) > 0 {
log.Debug(string(out))
}
return out, err
}
// parseJailList parses f2b-client status and return the 'jails' it has configured.
func parseJailList(data []byte) []string {
// output:
// Status
// |- Number of jail: 1
// `- Jail list: cyrus
jails := []string{}
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
text := strings.ToLower(scanner.Text())
if strings.Contains(text, "jail list:") {
k := strings.Index(text, "jail list:")
leftover := text[k+len("jail list:"):]
jails = strings.Split(leftover, ",")
}
}
if scanner.Err() != nil {
return nil
}
for i := range jails {
jails[i] = strings.TrimSpace(jails[i])
}
return jails
}
func parseJail(data []byte, jl string) *jail {
// output:
// Status for the jail: sshd
// |- Filter
// | |- Currently failed: 0
// | |- Total failed: 0
// | `- File list: /var/log/auth.log
// `- Actions
// |- Currently banned: 0
// |- Total banned: 0
// `- Banned IP list:
scanner := bufio.NewScanner(bytes.NewReader(data))
j := &jail{name: jl}
const (
failed = "total failed:"
banned = "total banned:"
failednow = "currently failed:"
bannednow = "currently banned:"
)
for scanner.Scan() {
text := strings.ToLower(scanner.Text())
switch {
case strings.Contains(text, failed):
i, err := parseInt(text, failed)
if err != nil {
log.Warning(err.Error())
return nil
}
j.failedTotal = float64(i)
case strings.Contains(text, banned):
i, err := parseInt(text, banned)
if err != nil {
log.Warning(err.Error())
return nil
}
j.bannedTotal = float64(i)
case strings.Contains(text, failednow):
i, err := parseInt(text, failednow)
if err != nil {
log.Warning(err.Error())
return nil
}
j.failedNow = float64(i)
case strings.Contains(text, bannednow):
i, err := parseInt(text, bannednow)
if err != nil {
log.Warning(err.Error())
return nil
}
j.bannedNow = float64(i)
}
}
// fail2ban outputs _all_ addreses blocked, this leads to token too long, but we actually don't parse that line
// just return the jail stuff we found.
if err := scanner.Err(); err != nil {
log.Warning(err.Error())
}
return j
}
func doit() {
out, err := fail2ban("status")
if err != nil {
// don't update metrics so timestamp will age.
log.Warningf("Failed to run fail2ban status")
return
}
names := parseJailList(out)
jails := []*jail{}
for i := range names {
out, err := fail2ban("status", names[i])
if err != nil {
// don't update metrics so timestamp will age.
log.Warningf("Failed to run fail2ban status ...")
continue
}
j := parseJail(out, names[i])
if j == nil {
log.Warningf("Failed to parse fail2ban jail output")
continue
}
jails = append(jails, j)
}
if len(jails) != len(names) {
log.Warningf("Not all jails are found or parsed %d != %d", len(jails), len(names))
return
}
log.Infof("Getting data for %d jails", len(jails))
for i := range jails {
j := jails[i]
metricFailedTotal.WithLabelValues(j.name).Set(float64(j.failedTotal))
metricBannedTotal.WithLabelValues(j.name).Set(float64(j.bannedTotal))
metricFailedNow.WithLabelValues(j.name).Set(float64(j.failedNow))
metricBannedNow.WithLabelValues(j.name).Set(float64(j.bannedNow))
}
metricLastRunTimestamp.Set(float64(time.Now().Unix()))
metricRunDuration.Set(float64(*flagDuration))
if !*flagWrite {
promfmt.Fprint(os.Stdout, promfmt.NewPrefixFilter("f2bne_"))
return
}
if err := promfmt.WriteFile(promfile, promfmt.NewPrefixFilter("f2bne_")); err != nil {
log.Fatalf("Failed to write to prom file: %s", err)
}
}
func parseInt(text, substring string) (int64, error) {
k := strings.Index(text, substring)
num := strings.TrimSpace(text[k+len(substring):])
i, err := strconv.ParseInt(num, 10, 64)
if err != nil {
return 0, err
}
return i, nil
}