Skip to content

Commit a0595a0

Browse files
committed
check_logfiles: add "allowed folders" option
1 parent 0347b2f commit a0595a0

File tree

14 files changed

+159
-33
lines changed

14 files changed

+159
-33
lines changed

Changes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ next:
88
- add list-combine option
99
- add challenge password support for csr
1010
- add CheckLogFile option to /modules configuration
11+
- add "allowed pattern" option for check_logfiles
1112
- add new macro operator trim and chomp
1213

1314
0.37 Sun Sep 7 11:41:00 CEST 2025

docs/checks/commands/check_logfile.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ title: logfile
66

77
Checks logfiles or any other text format file for errors or other general patterns
88

9+
In order to use this plugin, you need to enable 'CheckLogFile' in the '[/modules]' section of the snclient_local.ini.
10+
11+
Also, to avoid security issues, you need to set 'allowed pattern' in the '[/settings/check/logfile]'
12+
section of the snclient_local.ini to a comma separated list of allowed glob patterns.
13+
14+
Example:
15+
[/settings/check/logfile]
16+
allowed pattern = /var/log/** # This allows all files recursively in /var/log/
17+
allowed pattern += /opt/logs/*.log # This allows all files with .log extension in /opt/logs/
18+
19+
See https://github.com/bmatcuk/doublestar#patterns for details on the pattern syntax.
20+
21+
922
- [Examples](#examples)
1023
- [Argument Defaults](#argument-defaults)
1124
- [Attributes](#attributes)

docs/configuration/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ allowed hosts += , 192.168.0.2,192.168.0.3
113113
```
114114
115115
Values will simply be joined as text, so in case you want to create lists, make sure you
116-
add a comma.
116+
add a comma. (Starting with v0.38 this will be done automatically)
117117
118118
## Inheritance
119119

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.24.6
55
require (
66
github.com/beevik/ntp v1.5.0
77
github.com/bi-zone/go-fileversion v1.0.0
8+
github.com/bmatcuk/doublestar/v4 v4.9.1
89
github.com/consol-monitoring/check_nsc_web v0.7.4
910
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
1011
github.com/go-chi/chi/v5 v5.2.3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
88
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
99
github.com/bi-zone/go-fileversion v1.0.0 h1:N/sorBKYfWDjT5ySlUOxSJvG3g9XiaBKxXgHGfH5Wf8=
1010
github.com/bi-zone/go-fileversion v1.0.0/go.mod h1:evMpx4TA/iTAtufWYK271/Mbr5JAL24vlu1WNjVef6s=
11+
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
12+
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
1113
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
1214
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
1315
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=

packaging/snclient.ini

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ CheckLogFile = disabled
6363

6464

6565
[/settings/default]
66-
; allowed hosts - List of ips/networks/hostname allowed to connect.
67-
allowed hosts = 127.0.0.1, ::1
66+
; allowed hosts - Comma separated list of ips/networks/hostname allowed to connect.
67+
allowed hosts = 127.0.0.1
68+
allowed hosts += ::1
6869

6970
; cache allowed hosts - Cache resolved dns names.
7071
cache allowed hosts = true
@@ -313,6 +314,16 @@ require password = true
313314
disabled = false
314315
315316
317+
; check_logfile settings - configure settings for check_logfile
318+
; do not mix up with log settings for the snclient itself which are in [/settings/log]
319+
[/settings/check/logfile]
320+
321+
; allowed pattern - Comma separated list of glob pattern which are allowed to be checked by check_logfile
322+
allowed pattern = /var/log/**
323+
allowed pattern += ${shared-path}/*.log
324+
allowed pattern += /var/log/snclient/snclient.log
325+
326+
316327
; External script settings - General settings for the external scripts module (CheckExternalScripts).
317328
[/settings/external scripts]
318329

pkg/snclient/check_logfile.go

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"slices"
1212
"strings"
1313

14+
"github.com/bmatcuk/doublestar/v4"
1415
"github.com/consol-monitoring/snclient/pkg/convert"
1516
)
1617

@@ -45,9 +46,22 @@ func NewCheckLogFile() CheckHandler {
4546

4647
func (c *CheckLogFile) Build() *CheckData {
4748
return &CheckData{
48-
implemented: ALL,
49-
name: "check_logfile",
50-
description: "Checks logfiles or any other text format file for errors or other general patterns",
49+
implemented: ALL,
50+
name: "check_logfile",
51+
description: `Checks logfiles or any other text format file for errors or other general patterns
52+
53+
In order to use this plugin, you need to enable 'CheckLogFile' in the '[/modules]' section of the snclient_local.ini.
54+
55+
Also, to avoid security issues, you need to set 'allowed pattern' in the '[/settings/check/logfile]'
56+
section of the snclient_local.ini to a comma separated list of allowed glob patterns.
57+
58+
Example:
59+
[/settings/check/logfile]
60+
allowed pattern = /var/log/** # This allows all files recursively in /var/log/
61+
allowed pattern += /opt/logs/*.log # This allows all files with .log extension in /opt/logs/
62+
63+
See https://github.com/bmatcuk/doublestar#patterns for details on the pattern syntax.
64+
`,
5165
detailSyntax: "%(line | chomp | cut=200)", // cut after 200 chars
5266
listCombine: "\n",
5367
okSyntax: "%(status) - All %(count) / %(total) Lines OK",
@@ -104,10 +118,12 @@ func (c *CheckLogFile) Check(_ context.Context, snc *Agent, check *CheckData, _
104118
var err error
105119
patterns[parts[0]], err = regexp.Compile(parts[1])
106120
if err != nil {
107-
return nil, fmt.Errorf("could not compile regex from patter: %s", err.Error())
121+
return nil, fmt.Errorf("could not compile regex from pattern: %s", err.Error())
108122
}
109123
}
110124

125+
allowedPattern := c.getAllowedPattern()
126+
111127
totalLineCount := 0
112128
for _, fileName := range c.FilePath {
113129
if fileName == "" {
@@ -119,6 +135,9 @@ func (c *CheckLogFile) Check(_ context.Context, snc *Agent, check *CheckData, _
119135
return nil, fmt.Errorf("could not get files for pattern %s, error was: %s", fileName, err.Error())
120136
}
121137
for _, fileName := range files {
138+
if !c.matchPattern(fileName, allowedPattern) {
139+
return nil, fmt.Errorf("file %s does not match any allowed pattern", fileName)
140+
}
122141
tmpCount, err := c.addFile(fileName, check, patterns)
123142
if err != nil {
124143
return nil, fmt.Errorf("error for file %s, error was: %s", fileName, err.Error())
@@ -289,6 +308,7 @@ func (c *CheckLogFile) getCustomSplitFunction() bufio.SplitFunc {
289308
}
290309
}
291310

311+
// getRequiredColumnNumbers extracts all required column numbers from the check conditions
292312
func (c *CheckLogFile) getRequiredColumnNumbers(check *CheckData) []int {
293313
// extract all required threshold numbers
294314
columnNumbers := []int{}
@@ -309,3 +329,30 @@ func (c *CheckLogFile) getRequiredColumnNumbers(check *CheckData) []int {
309329

310330
return columnNumbers
311331
}
332+
333+
// getAllowedPattern returns the list of allowed patterns from the config
334+
func (c *CheckLogFile) getAllowedPattern() []string {
335+
allowedPatternRaw, _ := c.snc.config.Section("/settings/check/logfile").GetString("allowed pattern")
336+
allowedPattern := strings.Split(allowedPatternRaw, ",")
337+
338+
for i := range allowedPattern {
339+
allowedPattern[i] = strings.TrimSpace(allowedPattern[i])
340+
}
341+
342+
return allowedPattern
343+
}
344+
345+
// matchPattern checks if the given fileName matches any of the allowed patterns
346+
func (c *CheckLogFile) matchPattern(fileName string, allowedPattern []string) bool {
347+
for _, pattern := range allowedPattern {
348+
matched, err := doublestar.PathMatch(pattern, fileName)
349+
if err != nil {
350+
continue
351+
}
352+
if matched {
353+
return true
354+
}
355+
}
356+
357+
return false
358+
}

pkg/snclient/check_logfile_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
const testLogfileConfig = `
1010
[/modules]
1111
CheckLogFile = enabled
12+
13+
[/settings/check/logfile]
14+
allowed pattern = **
1215
`
1316

1417
func TestCheckLogFileDisabled(t *testing.T) {

pkg/snclient/config.go

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,21 @@ func (config *Config) SectionsByPrefix(prefix string) map[string]*ConfigSection
553553
return list
554554
}
555555

556+
func (config *Config) getCombine(section, key string) (combine, trim string) {
557+
switch section {
558+
case "/settings/check/logfile":
559+
if key == "allowed pattern" {
560+
return " , ", ", "
561+
}
562+
default:
563+
if key == "allowed hosts" {
564+
return " , ", ", "
565+
}
566+
}
567+
568+
return "", ""
569+
}
570+
556571
// parseString parses string from config section.
557572
func configParseString(val string) (string, error) {
558573
val = strings.TrimSpace(val)
@@ -661,7 +676,9 @@ func (config *Config) ReplaceMacrosDefault(section *ConfigSection, timezone *tim
661676
section.data[key] = val
662677

663678
raw := section.raw[key]
664-
raw = ReplaceMacros(raw, timezone, defaultMacros)
679+
for i, r := range raw {
680+
raw[i] = ReplaceMacros(r, timezone, defaultMacros)
681+
}
665682
section.raw[key] = raw
666683
}
667684
}
@@ -729,7 +746,7 @@ type ConfigSection struct {
729746
cfg *Config // reference to parent config collection
730747
name string // section name
731748
data ConfigData // actual config data
732-
raw ConfigData // raw config data (including quotes and such)
749+
raw map[string][]string // raw config data (including quotes and such)
733750
keys []string // keys from config data
734751
comments map[string][]string // comments sorted by config keys
735752
}
@@ -740,7 +757,7 @@ func NewConfigSection(cfg *Config, name string) *ConfigSection {
740757
cfg: cfg,
741758
name: name,
742759
data: make(map[string]string, 0),
743-
raw: make(map[string]string, 0),
760+
raw: make(map[string][]string, 0),
744761
keys: make([]string, 0),
745762
comments: make(map[string][]string, 0),
746763
}
@@ -758,13 +775,21 @@ func (cs *ConfigSection) String() string {
758775
data = append(data, cs.comments[key]...)
759776
val := cs.data[key]
760777
raw := cs.raw[key]
761-
if raw != "" {
762-
val = raw
763-
}
764-
if val == "" {
765-
data = append(data, fmt.Sprintf("%s =", key))
766-
} else {
767-
data = append(data, fmt.Sprintf("%s = %s", key, cs.data[key]))
778+
switch len(raw) {
779+
case 0, 1:
780+
if val == "" {
781+
data = append(data, fmt.Sprintf("%s =", key))
782+
} else {
783+
data = append(data, fmt.Sprintf("%s = %s", key, cs.data[key]))
784+
}
785+
default:
786+
for i, line := range raw {
787+
if i == 0 {
788+
data = append(data, fmt.Sprintf("%s = %s", key, line))
789+
} else {
790+
data = append(data, fmt.Sprintf("%s += %s", key, line))
791+
}
792+
}
768793
}
769794
}
770795

@@ -778,6 +803,7 @@ func (cs *ConfigSection) String() string {
778803
// be stored as is, including quotes.
779804
func (cs *ConfigSection) SetRaw(key, value string) error {
780805
rawValue := value
806+
newRawValue := []string{rawValue}
781807

782808
useAppend := false
783809
if strings.HasSuffix(key, "+") {
@@ -793,8 +819,13 @@ func (cs *ConfigSection) SetRaw(key, value string) error {
793819
if useAppend {
794820
curRaw, cur, ok := cs.GetStringRaw(key)
795821
if ok {
796-
value = cur + value
797-
rawValue = curRaw + rawValue
822+
newRawValue = append(curRaw, rawValue)
823+
combine, trim := cs.cfg.getCombine(cs.name, key)
824+
if combine == "" {
825+
value = cur + value
826+
} else {
827+
value = strings.TrimRight(cur, trim) + combine + strings.TrimLeft(value, trim)
828+
}
798829
}
799830
}
800831

@@ -803,7 +834,7 @@ func (cs *ConfigSection) SetRaw(key, value string) error {
803834
}
804835

805836
cs.data[key] = value
806-
cs.raw[key] = rawValue
837+
cs.raw[key] = newRawValue
807838

808839
return nil
809840
}
@@ -815,10 +846,10 @@ func (cs *ConfigSection) Set(key, value string) {
815846
}
816847

817848
cs.data[key] = value
818-
cs.raw[key] = ""
849+
cs.raw[key] = []string{value}
819850
}
820851

821-
// Insert is just like Set but trys to find the key in comments first and will uncomment that one
852+
// Insert is just like Set but tries to find the key in comments first and will uncomment that one
822853
func (cs *ConfigSection) Insert(key, value string) {
823854
if cs.HasKey(key) {
824855
cs.data[key] = value
@@ -939,11 +970,11 @@ func (cs *ConfigSection) GetString(key string) (val string, ok bool) {
939970
// GetStringRaw returns the raw string (including quotes)
940971
// along the clean string from config section.
941972
// it returns the value if found and sets ok to true.
942-
func (cs *ConfigSection) GetStringRaw(key string) (raw, val string, ok bool) {
973+
func (cs *ConfigSection) GetStringRaw(key string) (raw []string, val string, ok bool) {
943974
raw = cs.raw[key]
944975
val, ok = cs.data[key]
945-
if raw == "" {
946-
raw = val
976+
if len(raw) == 0 {
977+
raw = []string{val}
947978
}
948979
if ok && cs.isUsable(key, val) {
949980
macros := make([]map[string]string, 0)

pkg/snclient/config_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,14 @@ func TestConfigAppend(t *testing.T) {
381381
section := cfg.Section("/settings/default")
382382
allowed, _ := section.GetString("allowed hosts")
383383

384-
expected := "127.0.0.1, ::1, 192.168.0.1, 192.168.0.2,192.168.0.3"
385-
assert.Equalf(t, expected, allowed, "reading appended config")
384+
assert.Equalf(t, "127.0.0.1, ::1 , 192.168.0.1 , 192.168.0.2,192.168.0.3", allowed, "reading appended allowed hosts")
385+
386+
nasty, _ := section.GetString("nasty characters")
387+
assert.Equalf(t, "123456", nasty, "reading appended string config")
388+
389+
section = cfg.Section("/settings/check/logfile")
390+
allowed, _ = section.GetString("allowed pattern")
391+
assert.Equalf(t, "/var/log/** , **/*.log", allowed, "reading appended allowed pattern")
386392
}
387393

388394
func TestConfigLongLines(t *testing.T) {

0 commit comments

Comments
 (0)