Skip to content

Commit 3569dd9

Browse files
authored
Merge pull request #20 from fzipi/fix-nginx-time-comparision
Fix nginx time comparision
2 parents 473ed9f + 68197ad commit 3569dd9

File tree

9 files changed

+122
-20
lines changed

9 files changed

+122
-20
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@ logtype:
4646
timeformat: '%a %b %d %H:%M:%S.%f %Y'
4747
```
4848
49+
For nginx, as logs will be to the second, you need to add the amount of time you want to truncate to. This will for example discard anything less than one second:
50+
4951
```yaml
5052
---
5153
logfile: '../coreruleset/tests/logs/modsec3-nginx/nginx/error.log'
5254
logtype:
5355
name: 'nginx'
5456
timeregex: '(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})'
5557
timeformat: 'YYYY/MM/DD HH:mm:ss'
58+
timetruncate: 1s
5659
```
5760
5861
If your webserver uses a different time format, please [create an issue](https://github.com/fzipi/go-ftw/issues/new/choose) and we can extend the documentation to cover it.

check/base.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/fzipi/go-ftw/config"
77
"github.com/fzipi/go-ftw/test"
88
"github.com/fzipi/go-ftw/waflog"
9+
"github.com/rs/zerolog/log"
910
)
1011

1112
// FTWCheck is the base struct for checks
@@ -18,15 +19,17 @@ type FTWCheck struct {
1819
func NewCheck(c *config.FTWConfiguration) *FTWCheck {
1920
check := &FTWCheck{
2021
log: &waflog.FTWLogLines{
21-
FileName: c.LogFile,
22-
TimeRegex: c.LogType.TimeRegex,
23-
TimeFormat: c.LogType.TimeFormat,
24-
Since: time.Now(),
25-
Until: time.Now(),
22+
FileName: c.LogFile,
23+
TimeRegex: c.LogType.TimeRegex,
24+
TimeFormat: c.LogType.TimeFormat,
25+
Since: time.Now(),
26+
Until: time.Now(),
27+
TimeTruncate: c.LogType.TimeTruncate,
2628
},
2729
expected: &test.Output{},
2830
}
2931

32+
log.Trace().Msgf("check/base: truncate set to %s", check.log.TimeTruncate)
3033
return check
3134
}
3235

check/base_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package check
22

3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/fzipi/go-ftw/config"
8+
)
9+
310
var yamlApacheConfig = `
411
---
512
logfile: 'tests/logs/modsec2-apache/apache2/error.log'
@@ -16,4 +23,15 @@ logtype:
1623
name: 'nginx'
1724
timeregex: '(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})'
1825
timeformat: 'YYYY/MM/DD HH:mm:ss'
26+
timetruncate: 1s
1927
`
28+
29+
func TestNewCheck(t *testing.T) {
30+
config.ImportFromString(yamlNginxConfig)
31+
32+
c := NewCheck(config.FTWConfig)
33+
34+
if c.log.TimeTruncate != time.Second {
35+
t.Errorf("Failed")
36+
}
37+
}

check/logs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func (c *FTWCheck) AssertNoLogContains() bool {
1212

1313
// AssertLogContains returns true when the logs contain the string
1414
func (c *FTWCheck) AssertLogContains() bool {
15+
log.Trace().Msgf("ftw/check: check will truncate at %s", c.log.TimeTruncate)
1516
if c.expected.LogContains != "" {
1617
log.Debug().Msgf("ftw/check: log contains? -> %s", c.expected.LogContains)
1718
return c.log.Contains(c.expected.LogContains)

config/config.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
package config
22

3+
import (
4+
"time"
5+
)
6+
37
// FTWConfig is being exported to be used across the app
48
var FTWConfig *FTWConfiguration
59

610
// FTWConfiguration FTW global Configuration
711
type FTWConfiguration struct {
8-
LogFile string `yaml:"logfile"`
9-
LogType FTWLogType `yaml:"logtype"`
12+
LogFile string
13+
LogType FTWLogType
1014
}
1115

1216
// FTWLogType log readers must implement this one
17+
// TimeTruncate is a string that represents a golang time, e.g. 'time.Microsecond', 'time.Second', etc.
18+
// It will be used when comparing times to match logs
1319
type FTWLogType struct {
14-
Name string `yaml:"name"`
15-
TimeRegex string `yaml:"timeregex"`
16-
TimeFormat string `yaml:"timeformat"`
20+
Name string
21+
TimeRegex string
22+
TimeFormat string
23+
TimeTruncate time.Duration
1724
}

config/config_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package config
22

33
import (
44
"io/ioutil"
5+
"os"
56
"testing"
7+
"time"
68

79
"github.com/fzipi/go-ftw/utils"
810
)
@@ -25,6 +27,16 @@ logtype:
2527
timeformat: 'ddd MMM DD HH:mm:ss.S YYYY'
2628
`
2729

30+
var yamlTruncateConfig = `
31+
---
32+
logfile: 'tests/logs/modsec3-nginx/nginx/error.log'
33+
logtype:
34+
name: nginx
35+
timetruncate: 1s
36+
timeformat: 'ddd MMM DD HH:mm:ss'
37+
38+
`
39+
2840
var jsonConfig = `
2941
{"test": "type"}
3042
`
@@ -45,13 +57,13 @@ func TestInitConfig(t *testing.T) {
4557

4658
func TestInitBadFileConfig(t *testing.T) {
4759
filename, _ := utils.CreateTempFileWithContent(jsonConfig, "test-*.yaml")
48-
60+
defer os.Remove(filename)
4961
Init(filename)
5062
}
5163

5264
func TestInitBadConfig(t *testing.T) {
5365
filename, _ := utils.CreateTempFileWithContent(yamlBadConfig, "test-*.yaml")
54-
66+
defer os.Remove(filename)
5567
Init(filename)
5668

5769
if FTWConfig == nil {
@@ -81,3 +93,21 @@ func TestImportConfig(t *testing.T) {
8193
t.Errorf("Failed !")
8294
}
8395
}
96+
97+
func TestTimeTruncateConfig(t *testing.T) {
98+
filename, _ := utils.CreateTempFileWithContent(yamlTruncateConfig, "test-*.yaml")
99+
defer os.Remove(filename)
100+
Init(filename)
101+
102+
if FTWConfig.LogType.Name != "nginx" {
103+
t.Errorf("Failed !")
104+
}
105+
106+
if FTWConfig.LogType.TimeFormat != "ddd MMM DD HH:mm:ss" {
107+
t.Errorf("Failed !")
108+
}
109+
110+
if FTWConfig.LogType.TimeTruncate != time.Second {
111+
t.Errorf("Failed !")
112+
}
113+
}

config/init.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ func Init(cfgFile string) {
3131
if err != nil {
3232
log.Fatal().Msgf("ftw/config: fatal error decoding config: %s", err.Error())
3333
}
34+
if duration := viper.GetDuration("logtype.timetruncate"); duration != 0 {
35+
log.Info().Msgf("ftw/config: will truncate logs to %s", duration)
36+
} else {
37+
log.Info().Msgf("ftw/config: no duration found")
38+
}
3439
}
3540

3641
// ImportFromString initializes the configuration from a yaml formatted string. Useful for testing.

waflog/base.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ type FTWLogLines struct {
1111
TimeRegex string
1212
// Gostradamus time format, e.g. 'ddd MMM DD HH:mm:ss.S YYYY'
1313
TimeFormat string
14-
Since time.Time
15-
Until time.Time
14+
// Truncate time to this time.Duration. Example is nginx logs will be up to the second,
15+
// so you want to truncate using '1s'.
16+
TimeTruncate time.Duration
17+
Since time.Time
18+
Until time.Time
1619
}

waflog/read.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import (
1313

1414
// Contains looks in logfile for regex
1515
func (ll *FTWLogLines) Contains(match string) bool {
16-
log.Debug().Msgf("ftw/waflog: Looking at file %s, between %s and %s", ll.FileName, ll.Since, ll.Until)
16+
log.Trace().Msgf("ftw/waflog: truncating at %s", ll.TimeTruncate)
17+
log.Trace().Msgf("ftw/waflog: Looking at file %s, between %s and %s, truncating on %s", ll.FileName, ll.Since, ll.Until, ll.TimeTruncate.String())
1718
// this should be a flag
1819
lines := ll.getLinesSinceUntil()
19-
log.Debug().Msgf("ftw/waflog: got %d lines", len(lines))
20+
log.Trace().Msgf("ftw/waflog: got %d lines", len(lines))
2021

2122
result := false
2223
for _, line := range lines {
@@ -34,6 +35,28 @@ func (ll *FTWLogLines) Contains(match string) bool {
3435
return result
3536
}
3637

38+
func isBetweenOrEqual(dt gostradamus.DateTime, start gostradamus.DateTime, end gostradamus.DateTime, duration time.Duration) bool {
39+
log.Trace().Msgf("ftw/waflog: truncating to %s", duration)
40+
// First check if we need to truncate times
41+
dtTime := dt.Time().Truncate(duration)
42+
startTime := start.Time().Truncate(duration)
43+
endTime := end.Time().Truncate(duration)
44+
45+
isBetween := dtTime.After(startTime) && dtTime.Before(endTime)
46+
log.Trace().Msgf("ftw/waflog: time %s is between %s and %s? %t", dtTime,
47+
startTime, endTime, isBetween)
48+
49+
isEqualStart := dtTime.Equal(startTime)
50+
log.Trace().Msgf("ftw/waflog: time %s is equal to %s ? %t", dtTime,
51+
startTime, isEqualStart)
52+
53+
isEqualEnd := dtTime.Equal(endTime)
54+
log.Trace().Msgf("ftw/waflog: time %s is equal to %s ? %t", dtTime,
55+
startTime, isEqualEnd)
56+
57+
return isBetween || isEqualStart || isEqualEnd
58+
}
59+
3760
func (ll *FTWLogLines) getLinesSinceUntil() [][]byte {
3861
var found [][]byte
3962
logfile, err := os.Open(ll.FileName)
@@ -60,30 +83,39 @@ func (ll *FTWLogLines) getLinesSinceUntil() [][]byte {
6083
line, _, err := scanner.LineBytes()
6184
if err != nil {
6285
if err == io.EOF {
63-
log.Debug().Msgf("got to the beginning of file")
86+
log.Trace().Msgf("got to the beginning of file")
6487
} else {
6588
log.Debug().Err(err)
6689
}
6790
break
6891
}
6992
if matchedLine := compiledRegex.FindSubmatch(line); matchedLine != nil {
7093
date := matchedLine[1]
94+
log.Trace().Msgf("ftw/waflog: matched %s in line %s", date, matchedLine)
7195
// well, go doesn't want to have a proper time format, so we need to use gostradamus
7296
t, err := gostradamus.Parse(string(date), ll.TimeFormat)
7397
if err != nil {
74-
log.Error().Msgf("ftw/waflog: %s", err.Error())
98+
log.Error().Msgf("ftw/waflog: error parsing date %s", err.Error())
7599
// return with what we got up to now
76100
break
77101
}
78102
// compare dates now
79-
if t.IsBetween(gostradamus.DateTimeFromTime(ll.Since), gostradamus.DateTimeFromTime(ll.Until)) {
103+
since := gostradamus.DateTimeFromTime(ll.Since).InTimezone(gostradamus.Local())
104+
until := gostradamus.DateTimeFromTime(ll.Until).InTimezone(gostradamus.Local())
105+
// Comparision will need to truncate
106+
if isBetweenOrEqual(t, since, until, ll.TimeTruncate) {
80107
saneCopy := make([]byte, len(line))
81108
copy(saneCopy, line)
82109
found = append(found, saneCopy)
83110
continue
111+
} else {
112+
log.Trace().Msgf("ftw/waflog: time %s is not between %s and %s", t.Time(),
113+
since.Format(ll.TimeFormat),
114+
until.Format(ll.TimeFormat))
84115
}
85116
// if we are before since, we need to stop searching
86-
if t.IsBetween(gostradamus.DateTimeFromTime(time.Time{}), gostradamus.DateTimeFromTime(ll.Since)) {
117+
if t.IsBetween(gostradamus.DateTimeFromTime(time.Time{}).InTimezone(gostradamus.Local()),
118+
since) {
87119
break
88120
}
89121
}

0 commit comments

Comments
 (0)