Skip to content
Snippets Groups Projects
Commit 46182d0f authored by Miek Gieben's avatar Miek Gieben
Browse files

Merge branch 'f2b' into 'main'

Added first  tests

See merge request !5
parents 04c0aa6d dee2e0d3
No related branches found
No related tags found
1 merge request!5Added first tests
Pipeline #66512 passed
f2bne
Go program that exports f2b status logs into node exporter. Metrics are dumped on standard output.
The following metrics are dumped:
- `f2bne_last_run_timestamp_seconds <epoch>` (gauge)
- `f2bne_run_duration_seconds <duration>` (gauge)
- `f2bne_failed_count_total{jail="<jail>"} <num>` (counter)
- `f2bne_banned_count_total{jail="<jail>"} <num>` (counter)
# Nodeexporter Exporters
Generally we want node-exporter exporter to follow the following rules:
- accept a -t <duration> flag to tells it how often we expect it to run. This flag's value in only
used in the metrics.
- accept a -w flag that tells it to write to `/var/lib/prometheus/node-exporter/<basename>.prom`
- at least have the following metrics:
- `<name>_last_run_timestamp_seconds <epoch>` - when did we run
- `<name>_run_duration_seconds <duration>` - the value of -t
All *NE*s are packaged in cncz-ne package.
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")
)
const promfile = "/var/lib/prometheus/node-exporter/f2bne.prom"
func main() {
flag.Parse()
doit()
}
type jail struct {
name string
failedTotal float64
bannedTotal float64
}
func fail2ban(args ...string) ([]byte, error) {
ctx := context.TODO()
cmd := exec.CommandContext(ctx, "fail2ban-client", args...)
log.Debugf("running fail2ban %q %v", cmd.Dir, 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:") {
i := strings.Index(text, "jail list:")
leftover := text[i+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:"
)
for scanner.Scan() {
text := strings.ToLower(scanner.Text())
switch {
case strings.Contains(text, failed):
k := strings.Index(text, failed)
num := strings.TrimSpace(text[k+len(failed):])
i, err := strconv.ParseInt(num, 10, 64)
if err != nil {
println(err.Error())
return nil
}
j.failedTotal = float64(i)
case strings.Contains(text, banned):
k := strings.Index(text, banned)
num := strings.TrimSpace(text[k+len(banned):])
i, err := strconv.ParseInt(num, 10, 64)
if err != nil {
println(err.Error())
return nil
}
j.bannedTotal = float64(i)
}
}
if scanner.Err() != nil {
return nil
}
return j
}
func doit() {
out, err := fail2ban("status")
if err != nil {
// don't update metrics so timestamp will age.
log.Warningf("Failed to run failed2ban 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 failed2ban status ...")
continue
}
j := parseJail(out, names[i])
if j == nil {
log.Warningf("Failed to parse failed2ban jail output")
continue
}
jails = append(jails, j)
}
if len(jails) != len(names) {
log.Warningf("Not all jails are found %d != %d", len(jails), len(names))
return
}
for i := range jails {
j := jails[i]
metricsFailedTotal.WithLabelValues(j.name).Set(float64(j.failedTotal))
metricsBannedTotal.WithLabelValues(j.name).Set(float64(j.bannedTotal))
}
metricLastRunTimestamp.Set(float64(time.Now().Unix()))
metricRunDuration.Set(30)
f, err := os.OpenFile("/tmp/test", os.O_CREATE|os.O_RDWR, 0644)
//f, err := os.OpenFile(promfile, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
log.Fatalf("Failed to open file %s: %s", promfile, err)
}
defer f.Close()
tf := promfmt.NewPrefixFilter("f2bne_")
promfmt.Fprint(f, tf)
}
package main
import (
"testing"
)
const jaillist = "Status\n" +
"|- Number of jail: 5\n" +
"`- Jail list: sshd, wordpress-wplogin-http, wordpress-wplogin-https, wordpress-xmlrpc-http, wordpress-xmlrpc-https\n"
func TestParseJailList(t *testing.T) {
jails := parseJailList([]byte(jaillist))
if len(jails) != 5 {
t.Errorf("expected 5 jails, got %d", len(jails))
}
expect := []string{"sshd", "wordpress-wplogin-http", "wordpress-wplogin-https", "wordpress-xmlrpc-http", "wordpress-xmlrpc-https"}
for i := range jails {
if jails[i] != expect[i] {
t.Errorf("expected jail %q, got: %s", jails[i], expect[i])
}
}
}
const jailinfo = "Status for the jail: sshd\n" +
"|- Filter\n" +
"| |- Currently failed: 0\n" +
"| |- Total failed: 3\n" +
"| `- File list: /var/log/auth.log\n" +
"`- Actions\n" +
" |- Currently banned: 5\n" +
" |- Total banned: 15\n" +
" `- Banned IP list: \n"
func TestParseJail(t *testing.T) {
j := parseJail([]byte(jailinfo), "sshd")
if j == nil {
t.Fatal("expected jail, got none")
}
if j.name != "sshd" {
t.Errorf("expected name %q: got %s", "sshd", j.name)
}
if j.failedTotal != 3 {
t.Errorf("expected failedTotal %d: got %f", 3, j.failedTotal)
}
if j.bannedTotal != 15 {
t.Errorf("expected failedTotal %d: got %f", 15, j.failedTotal)
}
}
package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
metricLastRunTimestamp = promauto.NewGauge(prometheus.GaugeOpts{
Name: "f2bne_last_run_timestamp_seconds",
Help: "Epoch timestamp of the last run.",
})
metricRunDuration = promauto.NewGauge(prometheus.GaugeOpts{
Name: "f2bne_run_duration_seconds",
Help: "Gauge of current configured loop time.",
})
metricsFailedTotal = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "f2bne_failed_count_total",
Help: "Counter to total failed filters.",
}, []string{"jail"})
metricsBannedTotal = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "f2bne_banned_count_total",
Help: "Counter to total banned.",
}, []string{"jail"})
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment