Add input plugin for Fail2ban (#2875)
This commit is contained in:
parent
9211985c63
commit
ca9cec2c84
|
@ -21,6 +21,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/dovecot"
|
_ "github.com/influxdata/telegraf/plugins/inputs/dovecot"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/elasticsearch"
|
_ "github.com/influxdata/telegraf/plugins/inputs/elasticsearch"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/exec"
|
_ "github.com/influxdata/telegraf/plugins/inputs/exec"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/fail2ban"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/filestat"
|
_ "github.com/influxdata/telegraf/plugins/inputs/filestat"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/graylog"
|
_ "github.com/influxdata/telegraf/plugins/inputs/graylog"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
|
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Fail2ban Plugin
|
||||||
|
|
||||||
|
The fail2ban plugin gathers counts of failed and banned ip addresses from fail2ban.
|
||||||
|
|
||||||
|
This plugin run fail2ban-client command, and fail2ban-client require root access.
|
||||||
|
You have to grant telegraf to run fail2ban-client:
|
||||||
|
|
||||||
|
- Run telegraf as root. (deprecate)
|
||||||
|
- Configure sudo to grant telegraf to fail2ban-client.
|
||||||
|
|
||||||
|
### Using sudo
|
||||||
|
|
||||||
|
You may edit your sudo configuration with the following:
|
||||||
|
|
||||||
|
``` sudo
|
||||||
|
telegraf ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status *
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration:
|
||||||
|
|
||||||
|
``` toml
|
||||||
|
# Read metrics from fail2ban.
|
||||||
|
[[inputs.fail2ban]]
|
||||||
|
## fail2ban-client require root access.
|
||||||
|
## Setting 'use_sudo' to true will make use of sudo to run fail2ban-client.
|
||||||
|
## Users must configure sudo to allow telegraf user to run fail2ban-client with no password.
|
||||||
|
## This plugin run only "fail2ban-client status".
|
||||||
|
use_sudo = false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Measurements & Fields:
|
||||||
|
|
||||||
|
- fail2ban
|
||||||
|
- failed (integer, count)
|
||||||
|
- banned (integer, count)
|
||||||
|
|
||||||
|
### Tags:
|
||||||
|
|
||||||
|
- All measurements have the following tags:
|
||||||
|
- jail
|
||||||
|
|
||||||
|
### Example Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
# fail2ban-client status sshd
|
||||||
|
Status for the jail: sshd
|
||||||
|
|- Filter
|
||||||
|
| |- Currently failed: 5
|
||||||
|
| |- Total failed: 20
|
||||||
|
| `- File list: /var/log/secure
|
||||||
|
`- Actions
|
||||||
|
|- Currently banned: 2
|
||||||
|
|- Total banned: 10
|
||||||
|
`- Banned IP list: 192.168.0.1 192.168.0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./telegraf --config telegraf.conf --input-filter fail2ban --test
|
||||||
|
fail2ban,jail=sshd failed=5i,banned=2i 1495868667000000000
|
||||||
|
```
|
|
@ -0,0 +1,135 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fail2ban
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
execCommand = exec.Command // execCommand is used to mock commands in tests.
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fail2ban struct {
|
||||||
|
path string
|
||||||
|
UseSudo bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
## fail2ban-client require root access.
|
||||||
|
## Setting 'use_sudo' to true will make use of sudo to run fail2ban-client.
|
||||||
|
## Users must configure sudo to allow telegraf user to run fail2ban-client with no password.
|
||||||
|
## This plugin run only "fail2ban-client status".
|
||||||
|
use_sudo = false
|
||||||
|
`
|
||||||
|
|
||||||
|
var metricsTargets = []struct {
|
||||||
|
target string
|
||||||
|
field string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
target: "Currently failed:",
|
||||||
|
field: "failed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: "Currently banned:",
|
||||||
|
field: "banned",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2ban) Description() string {
|
||||||
|
return "Read metrics from fail2ban."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2ban) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2ban) Gather(acc telegraf.Accumulator) error {
|
||||||
|
if len(f.path) == 0 {
|
||||||
|
return errors.New("fail2ban-client not found: verify that fail2ban is installed and that fail2ban-client is in your PATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := f.path
|
||||||
|
var arg []string
|
||||||
|
|
||||||
|
if f.UseSudo {
|
||||||
|
name = "sudo"
|
||||||
|
arg = append(arg, f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := append(arg, "status")
|
||||||
|
|
||||||
|
cmd := execCommand(name, args...)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run command %s: %s - %s", strings.Join(cmd.Args, " "), err, string(out))
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(out), "\n")
|
||||||
|
const targetString = "Jail list:"
|
||||||
|
var jails []string
|
||||||
|
for _, line := range lines {
|
||||||
|
idx := strings.LastIndex(line, targetString)
|
||||||
|
if idx < 0 {
|
||||||
|
// not target line, skip.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jails = strings.Split(strings.TrimSpace(line[idx+len(targetString):]), ", ")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, jail := range jails {
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
args := append(arg, "status", jail)
|
||||||
|
cmd := execCommand(name, args...)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run command %s: %s - %s", strings.Join(cmd.Args, " "), err, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(out), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
key, value := extractCount(line)
|
||||||
|
if key != "" {
|
||||||
|
fields[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.AddFields("fail2ban", fields, map[string]string{"jail": jail})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractCount(line string) (string, int) {
|
||||||
|
for _, metricsTarget := range metricsTargets {
|
||||||
|
idx := strings.LastIndex(line, metricsTarget.target)
|
||||||
|
if idx < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ban := strings.TrimSpace(line[idx+len(metricsTarget.target):])
|
||||||
|
banCount, err := strconv.Atoi(ban)
|
||||||
|
if err != nil {
|
||||||
|
return "", -1
|
||||||
|
}
|
||||||
|
return metricsTarget.field, banCount
|
||||||
|
}
|
||||||
|
return "", -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
f := Fail2ban{}
|
||||||
|
path, _ := exec.LookPath("fail2ban-client")
|
||||||
|
if len(path) > 0 {
|
||||||
|
f.path = path
|
||||||
|
}
|
||||||
|
inputs.Add("fail2ban", func() telegraf.Input {
|
||||||
|
f := f
|
||||||
|
return &f
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package fail2ban
|
|
@ -0,0 +1,125 @@
|
||||||
|
package fail2ban
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// By all rights, we should use `string literal`, but the string contains "`".
|
||||||
|
var execStatusOutput = "Status\n" +
|
||||||
|
"|- Number of jail:\t3\n" +
|
||||||
|
"`- Jail list:\tdovecot, postfix, sshd"
|
||||||
|
var execStatusDovecotOutput = "Status for the jail: dovecot\n" +
|
||||||
|
"|- Filter\n" +
|
||||||
|
"| |- Currently failed:\t11\n" +
|
||||||
|
"| |- Total failed:\t22\n" +
|
||||||
|
"| `- File list:\t/var/log/maillog\n" +
|
||||||
|
"`- Actions\n" +
|
||||||
|
" |- Currently banned:\t0\n" +
|
||||||
|
" |- Total banned:\t100\n" +
|
||||||
|
" `- Banned IP list:"
|
||||||
|
var execStatusPostfixOutput = "Status for the jail: postfix\n" +
|
||||||
|
"|- Filter\n" +
|
||||||
|
"| |- Currently failed:\t4\n" +
|
||||||
|
"| |- Total failed:\t10\n" +
|
||||||
|
"| `- File list:\t/var/log/maillog\n" +
|
||||||
|
"`- Actions\n" +
|
||||||
|
" |- Currently banned:\t3\n" +
|
||||||
|
" |- Total banned:\t60\n" +
|
||||||
|
" `- Banned IP list:\t192.168.10.1 192.168.10.3"
|
||||||
|
var execStatusSshdOutput = "Status for the jail: sshd\n" +
|
||||||
|
"|- Filter\n" +
|
||||||
|
"| |- Currently failed:\t0\n" +
|
||||||
|
"| |- Total failed:\t5\n" +
|
||||||
|
"| `- File list:\t/var/log/secure\n" +
|
||||||
|
"`- Actions\n" +
|
||||||
|
" |- Currently banned:\t2\n" +
|
||||||
|
" |- Total banned:\t50\n" +
|
||||||
|
" `- Banned IP list:\t192.168.0.1 192.168.1.1"
|
||||||
|
|
||||||
|
func TestGather(t *testing.T) {
|
||||||
|
f := Fail2ban{
|
||||||
|
path: "/usr/bin/fail2ban-client",
|
||||||
|
}
|
||||||
|
|
||||||
|
execCommand = fakeExecCommand
|
||||||
|
defer func() { execCommand = exec.Command }()
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := f.Gather(&acc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields1 := map[string]interface{}{
|
||||||
|
"banned": 2,
|
||||||
|
"failed": 0,
|
||||||
|
}
|
||||||
|
tags1 := map[string]string{
|
||||||
|
"jail": "sshd",
|
||||||
|
}
|
||||||
|
|
||||||
|
fields2 := map[string]interface{}{
|
||||||
|
"banned": 3,
|
||||||
|
"failed": 4,
|
||||||
|
}
|
||||||
|
tags2 := map[string]string{
|
||||||
|
"jail": "postfix",
|
||||||
|
}
|
||||||
|
|
||||||
|
fields3 := map[string]interface{}{
|
||||||
|
"banned": 0,
|
||||||
|
"failed": 11,
|
||||||
|
}
|
||||||
|
tags3 := map[string]string{
|
||||||
|
"jail": "dovecot",
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.AssertContainsTaggedFields(t, "fail2ban", fields1, tags1)
|
||||||
|
acc.AssertContainsTaggedFields(t, "fail2ban", fields2, tags2)
|
||||||
|
acc.AssertContainsTaggedFields(t, "fail2ban", fields3, tags3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeExecCommand(command string, args ...string) *exec.Cmd {
|
||||||
|
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
||||||
|
cs = append(cs, args...)
|
||||||
|
cmd := exec.Command(os.Args[0], cs...)
|
||||||
|
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelperProcess(t *testing.T) {
|
||||||
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
args := os.Args
|
||||||
|
cmd, args := args[3], args[4:]
|
||||||
|
|
||||||
|
if !strings.HasSuffix(cmd, "fail2ban-client") {
|
||||||
|
fmt.Fprint(os.Stdout, "command not found")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 1 && args[0] == "status" {
|
||||||
|
fmt.Fprint(os.Stdout, execStatusOutput)
|
||||||
|
os.Exit(0)
|
||||||
|
} else if len(args) == 2 && args[0] == "status" {
|
||||||
|
if args[1] == "sshd" {
|
||||||
|
fmt.Fprint(os.Stdout, execStatusSshdOutput)
|
||||||
|
os.Exit(0)
|
||||||
|
} else if args[1] == "postfix" {
|
||||||
|
fmt.Fprint(os.Stdout, execStatusPostfixOutput)
|
||||||
|
os.Exit(0)
|
||||||
|
} else if args[1] == "dovecot" {
|
||||||
|
fmt.Fprint(os.Stdout, execStatusDovecotOutput)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(os.Stdout, "invalid argument")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
Loading…
Reference in New Issue