Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
88
plugins/inputs/fail2ban/README.md
Normal file
88
plugins/inputs/fail2ban/README.md
Normal file
|
@ -0,0 +1,88 @@
|
|||
# Fail2ban Input Plugin
|
||||
|
||||
This plugin gathers the count of failed and banned IP addresses using
|
||||
[fail2ban][fail2ban] by running the `fail2ban-client` command.
|
||||
|
||||
> [!NOTE]
|
||||
> The `fail2ban-client` requires root access, so please make sure to either
|
||||
> allow Telegraf to run that command using `sudo` without a password or by
|
||||
> running telegraf as root (not recommended).
|
||||
|
||||
⭐ Telegraf v1.4.0
|
||||
🏷️ network, system
|
||||
💻 all
|
||||
|
||||
[fail2ban]: https://www.fail2ban.org
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Read metrics from fail2ban.
|
||||
[[inputs.fail2ban]]
|
||||
## Use sudo to run fail2ban-client
|
||||
# use_sudo = false
|
||||
|
||||
## Use the given socket instead of the default one
|
||||
# socket = "/var/run/fail2ban/fail2ban.sock"
|
||||
```
|
||||
|
||||
## Using sudo
|
||||
|
||||
Make sure to set `use_sudo = true` in your configuration file.
|
||||
|
||||
You will also need to update your sudoers file. It is recommended to modify a
|
||||
file in the `/etc/sudoers.d` directory using `visudo`:
|
||||
|
||||
```bash
|
||||
sudo visudo -f /etc/sudoers.d/telegraf
|
||||
```
|
||||
|
||||
Add the following lines to the file, these commands allow the `telegraf` user
|
||||
to call `fail2ban-client` without needing to provide a password and disables
|
||||
logging of the call in the auth.log. Consult `man 8 visudo` and `man 5
|
||||
sudoers` for details.
|
||||
|
||||
```text
|
||||
Cmnd_Alias FAIL2BAN = /usr/bin/fail2ban-client status, /usr/bin/fail2ban-client status *
|
||||
telegraf ALL=(root) NOEXEC: NOPASSWD: FAIL2BAN
|
||||
Defaults!FAIL2BAN !logfile, !syslog, !pam_session
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
- fail2ban
|
||||
- tags:
|
||||
- jail
|
||||
- fields:
|
||||
- failed (integer, count)
|
||||
- banned (integer, count)
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
fail2ban,jail=sshd failed=5i,banned=2i 1495868667000000000
|
||||
```
|
||||
|
||||
### Execute the binary directly
|
||||
|
||||
```shell
|
||||
# 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
|
||||
```
|
146
plugins/inputs/fail2ban/fail2ban.go
Normal file
146
plugins/inputs/fail2ban/fail2ban.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package fail2ban
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
var (
|
||||
execCommand = exec.Command // execCommand is used to mock commands in tests.
|
||||
metricsTargets = []struct {
|
||||
target string
|
||||
field string
|
||||
}{
|
||||
{
|
||||
target: "Currently failed:",
|
||||
field: "failed",
|
||||
},
|
||||
{
|
||||
target: "Currently banned:",
|
||||
field: "banned",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const cmd = "fail2ban-client"
|
||||
|
||||
type Fail2ban struct {
|
||||
UseSudo bool `toml:"use_sudo"`
|
||||
Socket string `toml:"socket"`
|
||||
path string
|
||||
}
|
||||
|
||||
func (*Fail2ban) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (f *Fail2ban) Init() error {
|
||||
// Set defaults
|
||||
if f.path == "" {
|
||||
path, err := exec.LookPath(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("looking up %q failed: %w", cmd, err)
|
||||
}
|
||||
f.path = path
|
||||
}
|
||||
|
||||
// Check parameters
|
||||
if f.path == "" {
|
||||
return fmt.Errorf("%q not found", cmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 args []string
|
||||
|
||||
if f.UseSudo {
|
||||
name = "sudo"
|
||||
args = append(args, f.path)
|
||||
}
|
||||
|
||||
if f.Socket != "" {
|
||||
args = append(args, "--socket", f.Socket)
|
||||
}
|
||||
args = append(args, "status")
|
||||
|
||||
cmd := execCommand(name, args...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run command %q: %w - %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 {
|
||||
// Skip over empty jails
|
||||
if jail == "" {
|
||||
continue
|
||||
}
|
||||
fields := make(map[string]interface{})
|
||||
cmd := execCommand(name, append(args, jail)...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run command %q: %w - %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() {
|
||||
inputs.Add("fail2ban", func() telegraf.Input {
|
||||
return &Fail2ban{}
|
||||
})
|
||||
}
|
133
plugins/inputs/fail2ban/fail2ban_test.go
Normal file
133
plugins/inputs/fail2ban/fail2ban_test.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package fail2ban
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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
|
||||
|
||||
require.NoError(t, f.Init())
|
||||
require.NoError(t, f.Gather(&acc))
|
||||
|
||||
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(_ *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")
|
||||
//nolint:revive // os.Exit called intentionally
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) == 1 && args[0] == "status" {
|
||||
fmt.Fprint(os.Stdout, execStatusOutput)
|
||||
//nolint:revive // os.Exit called intentionally
|
||||
os.Exit(0)
|
||||
} else if len(args) == 2 && args[0] == "status" {
|
||||
if args[1] == "sshd" {
|
||||
fmt.Fprint(os.Stdout, execStatusSshdOutput)
|
||||
//nolint:revive // os.Exit called intentionally
|
||||
os.Exit(0)
|
||||
} else if args[1] == "postfix" {
|
||||
fmt.Fprint(os.Stdout, execStatusPostfixOutput)
|
||||
//nolint:revive // os.Exit called intentionally
|
||||
os.Exit(0)
|
||||
} else if args[1] == "dovecot" {
|
||||
fmt.Fprint(os.Stdout, execStatusDovecotOutput)
|
||||
//nolint:revive // os.Exit called intentionally
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(os.Stdout, "invalid argument")
|
||||
//nolint:revive // os.Exit called intentionally
|
||||
os.Exit(1)
|
||||
}
|
7
plugins/inputs/fail2ban/sample.conf
Normal file
7
plugins/inputs/fail2ban/sample.conf
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Read metrics from fail2ban.
|
||||
[[inputs.fail2ban]]
|
||||
## Use sudo to run fail2ban-client
|
||||
# use_sudo = false
|
||||
|
||||
## Use the given socket instead of the default one
|
||||
# socket = "/var/run/fail2ban/fail2ban.sock"
|
Loading…
Add table
Add a link
Reference in a new issue