@cryptotaxi247 / netdata-1 / commits / 355753638

add go.d fail2ban (#17501)

* add go.d fail2ban * update contexts

Ilya Mashchenko committed Apr 23, 2024 at 19:37 UTC 355753638683af0036d4626c3a3a7ce68dcc531e
17 files changed +833 -2
src/go/collectors/go.d.plugin/README.md
+1
@@ -72,6 +72,7 @@ see the appropriate collector readme.
72 | [energid](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/energid) | Energi Core |
73 | [envoy](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/envoy) | Envoy |
74 | [example](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/example) | - |
75 +| [fail2ban](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/fail2ban) | Fail2Ban Jails |
76 | [filecheck](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/filecheck) | Files and Directories |
77 | [fluentd](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/fluentd) | Fluentd |
78 | [freeradius](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/freeradius) | FreeRADIUS |
src/go/collectors/go.d.plugin/config/go.d.conf
+1
@@ -35,6 +35,7 @@ modules:
35 # elasticsearch: yes
36 # envoy: yes
37 # example: no
38 +# fail2ban: yes
39 # filecheck: yes
40 # fluentd: yes
41 # freeradius: yes
src/go/collectors/go.d.plugin/config/go.d/fail2ban.conf new
+5
@@ -0,0 +1,5 @@
1 +## All available configuration options, their descriptions and default values:
2 +## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/fail2ban#readme
3 +
4 +jobs:
5 + - name: fail2ban
src/go/collectors/go.d.plugin/modules/fail2ban/charts.go new
+75
@@ -0,0 +1,75 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package fail2ban
4 +
5 +import (
6 + "fmt"
7 + "strings"
8 +
9 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
10 +)
11 +
12 +const (
13 + prioJailBannedIPs = module.Priority + iota
14 + prioJailActiveFailures
15 +)
16 +
17 +var jailChartsTmpl = module.Charts{
18 + jailCurrentBannedIPs.Copy(),
19 + jailActiveFailures.Copy(),
20 +}
21 +
22 +var (
23 + jailCurrentBannedIPs = module.Chart{
24 + ID: "jail_%s_banned_ips",
25 + Title: "Fail2Ban Jail banned IPs",
26 + Units: "addresses",
27 + Fam: "bans",
28 + Ctx: "fail2ban.jail_banned_ips",
29 + Type: module.Line,
30 + Priority: prioJailBannedIPs,
31 + Dims: module.Dims{
32 + {ID: "jail_%s_currently_banned", Name: "banned"},
33 + },
34 + }
35 + jailActiveFailures = module.Chart{
36 + ID: "jail_%s_active_failures",
37 + Title: "Fail2Ban Jail active failures",
38 + Units: "failures",
39 + Fam: "failures",
40 + Ctx: "fail2ban.jail_active_failures",
41 + Type: module.Line,
42 + Priority: prioJailActiveFailures,
43 + Dims: module.Dims{
44 + {ID: "jail_%s_currently_failed", Name: "active_failures"},
45 + },
46 + }
47 +)
48 +
49 +func (f *Fail2Ban) addJailCharts(jail string) {
50 + charts := jailChartsTmpl.Copy()
51 +
52 + for _, chart := range *charts {
53 + chart.ID = fmt.Sprintf(chart.ID, jail)
54 + chart.Labels = []module.Label{
55 + {Key: "jail", Value: jail},
56 + }
57 + for _, dim := range chart.Dims {
58 + dim.ID = fmt.Sprintf(dim.ID, jail)
59 + }
60 + }
61 +
62 + if err := f.Charts().Add(*charts...); err != nil {
63 + f.Warning(err)
64 + }
65 +}
66 +
67 +func (f *Fail2Ban) removeJailCharts(jail string) {
68 + px := fmt.Sprintf("jail_%s_", jail)
69 + for _, chart := range *f.Charts() {
70 + if strings.HasPrefix(chart.ID, px) {
71 + chart.MarkRemove()
72 + chart.MarkNotCreated()
73 + }
74 + }
75 +}
src/go/collectors/go.d.plugin/modules/fail2ban/collect.go new
+163
@@ -0,0 +1,163 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package fail2ban
4 +
5 +import (
6 + "bufio"
7 + "bytes"
8 + "errors"
9 + "fmt"
10 + "strconv"
11 + "strings"
12 + "time"
13 +)
14 +
15 +func (f *Fail2Ban) collect() (map[string]int64, error) {
16 + now := time.Now()
17 +
18 + if now.Sub(f.lastDiscoverTime) > f.discoverEvery || f.forceDiscover {
19 + jails, err := f.discoverJails()
20 + if err != nil {
21 + return nil, err
22 + }
23 + f.jails = jails
24 + f.lastDiscoverTime = now
25 + f.forceDiscover = false
26 + }
27 +
28 + mx := make(map[string]int64)
29 +
30 + if err := f.collectJails(mx); err != nil {
31 + return nil, err
32 + }
33 +
34 + return mx, nil
35 +}
36 +
37 +func (f *Fail2Ban) discoverJails() ([]string, error) {
38 + bs, err := f.exec.status()
39 + if err != nil {
40 + return nil, err
41 + }
42 +
43 + jails, err := parseFail2banStatus(bs)
44 + if err != nil {
45 + return nil, err
46 + }
47 +
48 + if len(jails) == 0 {
49 + return nil, errors.New("no jails found")
50 + }
51 +
52 + f.Debugf("discovered %d jails: %v", len(jails), jails)
53 +
54 + return jails, nil
55 +}
56 +
57 +func (f *Fail2Ban) collectJails(mx map[string]int64) error {
58 + seen := make(map[string]bool)
59 +
60 + for _, jail := range f.jails {
61 + f.Debugf("querying status for jail '%s'", jail)
62 + bs, err := f.exec.jailStatus(jail)
63 + if err != nil {
64 + if errors.Is(err, errJailNotExist) {
65 + f.forceDiscover = true
66 + continue
67 + }
68 + return err
69 + }
70 +
71 + failed, banned, err := parseFail2banJailStatus(bs)
72 + if err != nil {
73 + return err
74 + }
75 +
76 + if !f.seenJails[jail] {
77 + f.seenJails[jail] = true
78 + f.addJailCharts(jail)
79 + }
80 + seen[jail] = true
81 +
82 + px := fmt.Sprintf("jail_%s_", jail)
83 +
84 + mx[px+"currently_failed"] = failed
85 + mx[px+"currently_banned"] = banned
86 + }
87 +
88 + for jail := range f.seenJails {
89 + if !seen[jail] {
90 + delete(f.seenJails, jail)
91 + f.removeJailCharts(jail)
92 + }
93 + }
94 +
95 + return nil
96 +}
97 +
98 +func parseFail2banJailStatus(jailStatus []byte) (failed, banned int64, err error) {
99 + const (
100 + failedSub = "Currently failed:"
101 + bannedSub = "Currently banned:"
102 + )
103 +
104 + var failedFound, bannedFound bool
105 +
106 + sc := bufio.NewScanner(bytes.NewReader(jailStatus))
107 +
108 + for sc.Scan() && !(failedFound && bannedFound) {
109 + text := strings.TrimSpace(sc.Text())
110 + if text == "" {
111 + continue
112 + }
113 +
114 + if !failedFound {
115 + if i := strings.Index(text, failedSub); i != -1 {
116 + failedFound = true
117 + s := strings.TrimSpace(text[i+len(failedSub):])
118 + if failed, err = strconv.ParseInt(s, 10, 64); err != nil {
119 + return 0, 0, fmt.Errorf("failed to parse currently failed value (%s): %v", s, err)
120 + }
121 + }
122 + }
123 + if !bannedFound {
124 + if i := strings.Index(text, bannedSub); i != -1 {
125 + bannedFound = true
126 + s := strings.TrimSpace(text[i+len(bannedSub):])
127 + if banned, err = strconv.ParseInt(s, 10, 64); err != nil {
128 + return 0, 0, fmt.Errorf("failed to parse currently banned value (%s): %v", s, err)
129 + }
130 + }
131 + }
132 + }
133 +
134 + if !failedFound || !bannedFound {
135 + return 0, 0, errors.New("failed to find failed and banned values")
136 + }
137 +
138 + return failed, banned, nil
139 +}
140 +
141 +func parseFail2banStatus(status []byte) ([]string, error) {
142 + const sub = "Jail list:"
143 +
144 + var jails []string
145 +
146 + sc := bufio.NewScanner(bytes.NewReader(status))
147 +
148 + for sc.Scan() {
149 + text := strings.TrimSpace(sc.Text())
150 +
151 + if i := strings.Index(text, sub); i != -1 {
152 + s := strings.ReplaceAll(text[i+len(sub):], ",", "")
153 + jails = strings.Fields(s)
154 + break
155 + }
156 + }
157 +
158 + if len(jails) == 0 {
159 + return nil, errors.New("failed to find jails")
160 + }
161 +
162 + return jails, nil
163 +}
src/go/collectors/go.d.plugin/modules/fail2ban/config_schema.json new
+35
@@ -0,0 +1,35 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "Fail2Ban collector configuration.",
5 + "type": "object",
6 + "properties": {
7 + "update_every": {
8 + "title": "Update every",
9 + "description": "Data collection interval, measured in seconds.",
10 + "type": "integer",
11 + "minimum": 1,
12 + "default": 10
13 + },
14 + "timeout": {
15 + "title": "Timeout",
16 + "description": "Timeout for executing the binary, specified in seconds.",
17 + "type": "number",
18 + "minimum": 0.5,
19 + "default": 2
20 + }
21 + },
22 + "additionalProperties": false,
23 + "patternProperties": {
24 + "^name$": {}
25 + }
26 + },
27 + "uiSchema": {
28 + "uiOptions": {
29 + "fullPage": true
30 + },
31 + "timeout": {
32 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
33 + }
34 + }
35 +}
src/go/collectors/go.d.plugin/modules/fail2ban/exec.go new
+57
@@ -0,0 +1,57 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package fail2ban
4 +
5 +import (
6 + "context"
7 + "errors"
8 + "fmt"
9 + "os/exec"
10 + "strings"
11 + "time"
12 +
13 + "github.com/netdata/netdata/go/go.d.plugin/logger"
14 +)
15 +
16 +var errJailNotExist = errors.New("jail not exist")
17 +
18 +func newFail2BanClientCliExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *fail2banClientCliExec {
19 + return &fail2banClientCliExec{
20 + Logger: log,
21 + ndsudoPath: ndsudoPath,
22 + timeout: timeout,
23 + }
24 +}
25 +
26 +type fail2banClientCliExec struct {
27 + *logger.Logger
28 +
29 + ndsudoPath string
30 + timeout time.Duration
31 +}
32 +
33 +func (e *fail2banClientCliExec) status() ([]byte, error) {
34 + return e.execute("fail2ban-client-status")
35 +}
36 +
37 +func (e *fail2banClientCliExec) jailStatus(jail string) ([]byte, error) {
38 + return e.execute("fail2ban-client-status-jail", "--jail", jail)
39 +}
40 +
41 +func (e *fail2banClientCliExec) execute(args ...string) ([]byte, error) {
42 + ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
43 + defer cancel()
44 +
45 + cmd := exec.CommandContext(ctx, e.ndsudoPath, args...)
46 + e.Debugf("executing '%s'", cmd)
47 +
48 + bs, err := cmd.Output()
49 + if err != nil {
50 + if strings.HasPrefix(strings.TrimSpace(string(bs)), "Sorry but the jail") {
51 + return nil, errJailNotExist
52 + }
53 + return nil, fmt.Errorf("error on '%s': %v", cmd, err)
54 + }
55 +
56 + return bs, nil
57 +}
src/go/collectors/go.d.plugin/modules/fail2ban/fail2ban.go new
+111
@@ -0,0 +1,111 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package fail2ban
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
11 + "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
12 +)
13 +
14 +//go:embed "config_schema.json"
15 +var configSchema string
16 +
17 +func init() {
18 + module.Register("fail2ban", module.Creator{
19 + JobConfigSchema: configSchema,
20 + Defaults: module.Defaults{
21 + UpdateEvery: 10,
22 + },
23 + Create: func() module.Module { return New() },
24 + })
25 +}
26 +
27 +func New() *Fail2Ban {
28 + return &Fail2Ban{
29 + Config: Config{
30 + Timeout: web.Duration(time.Second * 2),
31 + },
32 + charts: &module.Charts{},
33 + discoverEvery: time.Minute * 5,
34 + seenJails: make(map[string]bool),
35 + }
36 +}
37 +
38 +type Config struct {
39 + UpdateEvery int `yaml:"update_every" json:"update_every"`
40 + Timeout web.Duration `yaml:"timeout" json:"timeout"`
41 +}
42 +
43 +type (
44 + Fail2Ban struct {
45 + module.Base
46 + Config `yaml:",inline" json:""`
47 +
48 + charts *module.Charts
49 +
50 + exec fail2banClientCli
51 +
52 + discoverEvery time.Duration
53 + lastDiscoverTime time.Time
54 + forceDiscover bool
55 + jails []string
56 +
57 + seenJails map[string]bool
58 + }
59 + fail2banClientCli interface {
60 + status() ([]byte, error)
61 + jailStatus(s string) ([]byte, error)
62 + }
63 +)
64 +
65 +func (f *Fail2Ban) Configuration() any {
66 + return f.Config
67 +}
68 +
69 +func (f *Fail2Ban) Init() error {
70 + f2bClientExec, err := f.initFail2banClientCliExec()
71 + if err != nil {
72 + f.Errorf("fail2ban-client exec initialization: %v", err)
73 + return err
74 + }
75 + f.exec = f2bClientExec
76 +
77 + return nil
78 +}
79 +
80 +func (f *Fail2Ban) Check() error {
81 + mx, err := f.collect()
82 + if err != nil {
83 + f.Error(err)
84 + return err
85 + }
86 +
87 + if len(mx) == 0 {
88 + return errors.New("no metrics collected")
89 + }
90 +
91 + return nil
92 +}
93 +
94 +func (f *Fail2Ban) Charts() *module.Charts {
95 + return f.charts
96 +}
97 +
98 +func (f *Fail2Ban) Collect() map[string]int64 {
99 + mx, err := f.collect()
100 + if err != nil {
101 + f.Error(err)
102 + }
103 +
104 + if len(mx) == 0 {
105 + return nil
106 + }
107 +
108 + return mx
109 +}
110 +
111 +func (f *Fail2Ban) Cleanup() {}
src/go/collectors/go.d.plugin/modules/fail2ban/fail2ban_test.go new
+238
@@ -0,0 +1,238 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package fail2ban
4 +
5 +import (
6 + "errors"
7 + "os"
8 + "testing"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/module"
11 +
12 + "github.com/stretchr/testify/assert"
13 + "github.com/stretchr/testify/require"
14 +)
15 +
16 +var (
17 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
18 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
19 +
20 + dataStatus, _ = os.ReadFile("testdata/fail2ban-status.txt")
21 + dataJailStatus, _ = os.ReadFile("testdata/fail2ban-jail-status.txt")
22 +)
23 +
24 +func Test_testDataIsValid(t *testing.T) {
25 + for name, data := range map[string][]byte{
26 + "dataConfigJSON": dataConfigJSON,
27 + "dataConfigYAML": dataConfigYAML,
28 +
29 + "dataStatus": dataStatus,
30 + "dataJailStatus": dataJailStatus,
31 + } {
32 + require.NotNil(t, data, name)
33 +
34 + }
35 +}
36 +
37 +func TestFail2Ban_Configuration(t *testing.T) {
38 + module.TestConfigurationSerialize(t, &Fail2Ban{}, dataConfigJSON, dataConfigYAML)
39 +}
40 +
41 +func TestFail2Ban_Init(t *testing.T) {
42 + tests := map[string]struct {
43 + config Config
44 + wantFail bool
45 + }{
46 + "fails if failed to locate ndsudo": {
47 + wantFail: true,
48 + config: New().Config,
49 + },
50 + }
51 +
52 + for name, test := range tests {
53 + t.Run(name, func(t *testing.T) {
54 + f2b := New()
55 + f2b.Config = test.config
56 +
57 + if test.wantFail {
58 + assert.Error(t, f2b.Init())
59 + } else {
60 + assert.NoError(t, f2b.Init())
61 + }
62 + })
63 + }
64 +}
65 +
66 +func TestFail2Ban_Cleanup(t *testing.T) {
67 + tests := map[string]struct {
68 + prepare func() *Fail2Ban
69 + }{
70 + "not initialized exec": {
71 + prepare: func() *Fail2Ban {
72 + return New()
73 + },
74 + },
75 + "after check": {
76 + prepare: func() *Fail2Ban {
77 + f2b := New()
78 + f2b.exec = prepareMockOk()
79 + _ = f2b.Check()
80 + return f2b
81 + },
82 + },
83 + "after collect": {
84 + prepare: func() *Fail2Ban {
85 + f2b := New()
86 + f2b.exec = prepareMockOk()
87 + _ = f2b.Collect()
88 + return f2b
89 + },
90 + },
91 + }
92 +
93 + for name, test := range tests {
94 + t.Run(name, func(t *testing.T) {
95 + f2b := test.prepare()
96 +
97 + assert.NotPanics(t, f2b.Cleanup)
98 + })
99 + }
100 +}
101 +
102 +func TestFail2Ban_Charts(t *testing.T) {
103 + assert.NotNil(t, New().Charts())
104 +}
105 +
106 +func TestFail2Ban_Check(t *testing.T) {
107 + tests := map[string]struct {
108 + prepareMock func() *mockFail2BanClientCliExec
109 + wantFail bool
110 + }{
111 + "success multiple jails": {
112 + wantFail: false,
113 + prepareMock: prepareMockOk,
114 + },
115 + "error on status": {
116 + wantFail: true,
117 + prepareMock: prepareMockErrOnStatus,
118 + },
119 + "empty response (no jails)": {
120 + prepareMock: prepareMockEmptyResponse,
121 + wantFail: true,
122 + },
123 + }
124 +
125 + for name, test := range tests {
126 + t.Run(name, func(t *testing.T) {
127 + f2b := New()
128 + mock := test.prepareMock()
129 + f2b.exec = mock
130 +
131 + if test.wantFail {
132 + assert.Error(t, f2b.Check())
133 + } else {
134 + assert.NoError(t, f2b.Check())
135 + }
136 + })
137 + }
138 +}
139 +
140 +func TestFail2Ban_Collect(t *testing.T) {
141 + tests := map[string]struct {
142 + prepareMock func() *mockFail2BanClientCliExec
143 + wantMetrics map[string]int64
144 + }{
145 + "success multiple jails": {
146 + prepareMock: prepareMockOk,
147 + wantMetrics: map[string]int64{
148 + "jail_dovecot_currently_banned": 30,
149 + "jail_dovecot_currently_failed": 10,
150 + "jail_sshd_currently_banned": 30,
151 + "jail_sshd_currently_failed": 10,
152 + },
153 + },
154 + "error on status": {
155 + prepareMock: prepareMockErrOnStatus,
156 + wantMetrics: nil,
157 + },
158 + "empty response (no jails)": {
159 + prepareMock: prepareMockEmptyResponse,
160 + wantMetrics: nil,
161 + },
162 + }
163 +
164 + for name, test := range tests {
165 + t.Run(name, func(t *testing.T) {
166 + f2b := New()
167 + mock := test.prepareMock()
168 + f2b.exec = mock
169 +
170 + mx := f2b.Collect()
171 +
172 + assert.Equal(t, test.wantMetrics, mx)
173 + if len(test.wantMetrics) > 0 {
174 + assert.Len(t, *f2b.Charts(), len(jailChartsTmpl)*2)
175 + testMetricsHasAllChartsDims(t, f2b, mx)
176 + }
177 + })
178 + }
179 +}
180 +
181 +func testMetricsHasAllChartsDims(t *testing.T, f2b *Fail2Ban, mx map[string]int64) {
182 + for _, chart := range *f2b.Charts() {
183 + if chart.Obsolete {
184 + continue
185 + }
186 + for _, dim := range chart.Dims {
187 + _, ok := mx[dim.ID]
188 + assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
189 + }
190 + for _, v := range chart.Vars {
191 + _, ok := mx[v.ID]
192 + assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
193 + }
194 + }
195 +}
196 +
197 +func prepareMockOk() *mockFail2BanClientCliExec {
198 + return &mockFail2BanClientCliExec{
199 + statusData: dataStatus,
200 + jailStatusData: dataJailStatus,
201 + }
202 +}
203 +
204 +func prepareMockErrOnStatus() *mockFail2BanClientCliExec {
205 + return &mockFail2BanClientCliExec{
206 + errOnStatus: true,
207 + statusData: dataStatus,
208 + jailStatusData: dataJailStatus,
209 + }
210 +}
211 +
212 +func prepareMockEmptyResponse() *mockFail2BanClientCliExec {
213 + return &mockFail2BanClientCliExec{}
214 +}
215 +
216 +type mockFail2BanClientCliExec struct {
217 + errOnStatus bool
218 + statusData []byte
219 +
220 + errOnJailStatus bool
221 + jailStatusData []byte
222 +}
223 +
224 +func (m *mockFail2BanClientCliExec) status() ([]byte, error) {
225 + if m.errOnStatus {
226 + return nil, errors.New("mock.status() error")
227 + }
228 +
229 + return m.statusData, nil
230 +}
231 +
232 +func (m *mockFail2BanClientCliExec) jailStatus(_ string) ([]byte, error) {
233 + if m.errOnJailStatus {
234 + return nil, errors.New("mock.jailStatus() error")
235 + }
236 +
237 + return m.jailStatusData, nil
238 +}
src/go/collectors/go.d.plugin/modules/fail2ban/init.go new
+23
@@ -0,0 +1,23 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package fail2ban
4 +
5 +import (
6 + "fmt"
7 + "os"
8 + "path/filepath"
9 +
10 + "github.com/netdata/netdata/go/go.d.plugin/agent/executable"
11 +)
12 +
13 +func (f *Fail2Ban) initFail2banClientCliExec() (fail2banClientCli, error) {
14 + ndsudoPath := filepath.Join(executable.Directory, "ndsudo")
15 + if _, err := os.Stat(ndsudoPath); err != nil {
16 + return nil, fmt.Errorf("ndsudo executable not found: %v", err)
17 +
18 + }
19 +
20 + f2bClientExec := newFail2BanClientCliExec(ndsudoPath, f.Timeout.Duration(), f.Logger)
21 +
22 + return f2bClientExec, nil
23 +}
src/go/collectors/go.d.plugin/modules/fail2ban/metadata.yaml new
+105
@@ -0,0 +1,105 @@
1 +plugin_name: go.d.plugin
2 +modules:
3 + - meta:
4 + id: collector-go.d.plugin-fail2ban
5 + plugin_name: go.d.plugin
6 + module_name: fail2ban
7 + monitored_instance:
8 + name: Fail2ban
9 + link: "https://github.com/fail2ban/fail2ban#readme"
10 + icon_filename: fail2ban.png
11 + categories:
12 + - data-collection.authentication-and-authorization
13 + keywords:
14 + - fail2ban
15 + - security
16 + - authentication
17 + - authorization
18 + related_resources:
19 + integrations:
20 + list: []
21 + info_provided_to_referring_integrations:
22 + description: ""
23 + most_popular: false
24 + overview:
25 + data_collection:
26 + metrics_description: >
27 + This collector tracks two main metrics for each jail: currently banned IPs and active failure incidents.
28 + It relies on the [`fail2ban-client`](https://linux.die.net/man/1/fail2ban-client) CLI tool but avoids directly executing the binary.
29 + Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment.
30 + This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management.
31 + method_description: ""
32 + supported_platforms:
33 + include: []
34 + exclude: []
35 + multi_instance: false
36 + additional_permissions:
37 + description: ""
38 + default_behavior:
39 + auto_detection:
40 + description: ""
41 + limits:
42 + description: ""
43 + performance_impact:
44 + description: ""
45 + setup:
46 + prerequisites:
47 + list: []
48 + configuration:
49 + file:
50 + name: go.d/fail2ban.conf
51 + options:
52 + description: |
53 + The following options can be defined globally: update_every.
54 + folding:
55 + title: Config options
56 + enabled: true
57 + list:
58 + - name: update_every
59 + description: Data collection frequency.
60 + default_value: 10
61 + required: false
62 + - name: timeout
63 + description: fail2ban-client binary execution timeout.
64 + default_value: 2
65 + required: false
66 + examples:
67 + folding:
68 + title: Config
69 + enabled: true
70 + list:
71 + - name: Custom update_every
72 + description: Allows you to override the default data collection interval.
73 + config: |
74 + jobs:
75 + - name: fail2ban
76 + update_every: 5 # Collect Fail2Ban jails statistics every 5 seconds
77 + troubleshooting:
78 + problems:
79 + list: []
80 + alerts: []
81 + metrics:
82 + folding:
83 + title: Metrics
84 + enabled: false
85 + description: ""
86 + availability: []
87 + scopes:
88 + - name: jail
89 + description: These metrics refer to the Jail.
90 + labels:
91 + - name: jail
92 + description: Jail's name
93 + metrics:
94 + - name: fail2ban.jail_banned_ips
95 + description: Fail2Ban Jail banned IPs
96 + unit: addresses
97 + chart_type: line
98 + dimensions:
99 + - name: banned
100 + - name: fail2ban.jail_active_failures
101 + description: Fail2Ban Jail active failures
102 + unit: failures
103 + chart_type: line
104 + dimensions:
105 + - name: active_failures
src/go/collectors/go.d.plugin/modules/fail2ban/testdata/config.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "update_every": 123,
3 + "timeout": 123.123
4 +}
src/go/collectors/go.d.plugin/modules/fail2ban/testdata/config.yaml new
+2
@@ -0,0 +1,2 @@
1 +update_every: 123
2 +timeout: 123.123
src/go/collectors/go.d.plugin/modules/fail2ban/testdata/fail2ban-jail-status.txt new
+9
@@ -0,0 +1,9 @@
1 +Status for the jail: JAIL
2 +|- Filter
3 +| |- Currently failed: 10
4 +| |- Total failed: 20
5 +| `- File list: /var/log/auth.log
6 +`- Actions
7 + |- Currently banned: 30
8 + |- Total banned: 40
9 + `- Banned IP list:
src/go/collectors/go.d.plugin/modules/fail2ban/testdata/fail2ban-status.txt new
+3
@@ -0,0 +1,3 @@
1 +Status
2 +|- Number of jail: 1
3 +`- Jail list: sshd, dovecot
\ No newline at end of file
src/go/collectors/go.d.plugin/modules/init.go
+1
@@ -24,6 +24,7 @@ import (
24 _ "github.com/netdata/netdata/go/go.d.plugin/modules/elasticsearch"
25 _ "github.com/netdata/netdata/go/go.d.plugin/modules/envoy"
26 _ "github.com/netdata/netdata/go/go.d.plugin/modules/example"
27 + _ "github.com/netdata/netdata/go/go.d.plugin/modules/fail2ban"
28 _ "github.com/netdata/netdata/go/go.d.plugin/modules/filecheck"
29 _ "github.com/netdata/netdata/go/go.d.plugin/modules/fluentd"
30 _ "github.com/netdata/netdata/go/go.d.plugin/modules/freeradius"
src/go/collectors/go.d.plugin/modules/lvm/lvm_test.go
-2
@@ -61,7 +61,6 @@ func TestLVM_Init(t *testing.T) {
61 }
62 })
63 }
64 -
64 }
65
66 func TestLVM_Cleanup(t *testing.T) {
@@ -190,7 +189,6 @@ func TestLVM_Collect(t *testing.T) {
189 }
190 })
191 }
193 -
192 }
193
194 func prepareMockOK() *mockLvmCliExec {