github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/journalctl/journalctl_test.go (about) 1 package journalctlacquisition 2 3 import ( 4 "os" 5 "os/exec" 6 "path/filepath" 7 "runtime" 8 "testing" 9 "time" 10 11 "github.com/crowdsecurity/go-cs-lib/cstest" 12 13 "github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration" 14 "github.com/crowdsecurity/crowdsec/pkg/types" 15 log "github.com/sirupsen/logrus" 16 "github.com/sirupsen/logrus/hooks/test" 17 "github.com/stretchr/testify/assert" 18 "gopkg.in/tomb.v2" 19 ) 20 21 func TestBadConfiguration(t *testing.T) { 22 if runtime.GOOS == "windows" { 23 t.Skip("Skipping test on windows") 24 } 25 26 tests := []struct { 27 config string 28 expectedErr string 29 }{ 30 { 31 config: `foobar: asd.log`, 32 expectedErr: "line 1: field foobar not found in type journalctlacquisition.JournalCtlConfiguration", 33 }, 34 { 35 config: ` 36 mode: tail 37 source: journalctl`, 38 expectedErr: "journalctl_filter is required", 39 }, 40 { 41 config: ` 42 mode: cat 43 source: journalctl 44 journalctl_filter: 45 - _UID=42`, 46 expectedErr: "", 47 }, 48 } 49 50 subLogger := log.WithFields(log.Fields{ 51 "type": "journalctl", 52 }) 53 54 for _, test := range tests { 55 f := JournalCtlSource{} 56 err := f.Configure([]byte(test.config), subLogger, configuration.METRICS_NONE) 57 cstest.AssertErrorContains(t, err, test.expectedErr) 58 } 59 } 60 61 func TestConfigureDSN(t *testing.T) { 62 if runtime.GOOS == "windows" { 63 t.Skip("Skipping test on windows") 64 } 65 66 tests := []struct { 67 dsn string 68 expectedErr string 69 }{ 70 { 71 dsn: "asd://", 72 expectedErr: "invalid DSN asd:// for journalctl source, must start with journalctl://", 73 }, 74 { 75 dsn: "journalctl://", 76 expectedErr: "empty journalctl:// DSN", 77 }, 78 { 79 dsn: "journalctl://foobar=42", 80 expectedErr: "unsupported key foobar in journalctl DSN", 81 }, 82 { 83 dsn: "journalctl://filters=%ZZ", 84 expectedErr: "could not parse journalctl DSN : invalid URL escape \"%ZZ\"", 85 }, 86 { 87 dsn: "journalctl://filters=_UID=42?log_level=warn", 88 expectedErr: "", 89 }, 90 { 91 dsn: "journalctl://filters=_UID=1000&log_level=foobar", 92 expectedErr: "unknown level foobar: not a valid logrus Level:", 93 }, 94 { 95 dsn: "journalctl://filters=_UID=1000&log_level=warn&since=yesterday", 96 expectedErr: "", 97 }, 98 } 99 100 subLogger := log.WithFields(log.Fields{ 101 "type": "journalctl", 102 }) 103 104 for _, test := range tests { 105 f := JournalCtlSource{} 106 err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger, "") 107 cstest.AssertErrorContains(t, err, test.expectedErr) 108 } 109 } 110 111 func TestOneShot(t *testing.T) { 112 if runtime.GOOS == "windows" { 113 t.Skip("Skipping test on windows") 114 } 115 116 tests := []struct { 117 config string 118 expectedErr string 119 expectedOutput string 120 expectedLines int 121 logLevel log.Level 122 }{ 123 { 124 config: ` 125 source: journalctl 126 mode: cat 127 journalctl_filter: 128 - "-_UID=42"`, 129 expectedErr: "", 130 expectedOutput: "journalctl: invalid option", 131 logLevel: log.WarnLevel, 132 expectedLines: 0, 133 }, 134 { 135 config: ` 136 source: journalctl 137 mode: cat 138 journalctl_filter: 139 - _SYSTEMD_UNIT=ssh.service`, 140 expectedErr: "", 141 expectedOutput: "", 142 logLevel: log.WarnLevel, 143 expectedLines: 14, 144 }, 145 } 146 for _, ts := range tests { 147 var ( 148 logger *log.Logger 149 subLogger *log.Entry 150 hook *test.Hook 151 ) 152 153 if ts.expectedOutput != "" { 154 logger, hook = test.NewNullLogger() 155 logger.SetLevel(ts.logLevel) 156 subLogger = logger.WithFields(log.Fields{ 157 "type": "journalctl", 158 }) 159 } else { 160 subLogger = log.WithFields(log.Fields{ 161 "type": "journalctl", 162 }) 163 } 164 165 tomb := tomb.Tomb{} 166 out := make(chan types.Event, 100) 167 j := JournalCtlSource{} 168 169 err := j.Configure([]byte(ts.config), subLogger, configuration.METRICS_NONE) 170 if err != nil { 171 t.Fatalf("Unexpected error : %s", err) 172 } 173 174 err = j.OneShotAcquisition(out, &tomb) 175 cstest.AssertErrorContains(t, err, ts.expectedErr) 176 177 if err != nil { 178 continue 179 } 180 181 if ts.expectedLines != 0 { 182 assert.Len(t, out, ts.expectedLines) 183 } 184 185 if ts.expectedOutput != "" { 186 if hook.LastEntry() == nil { 187 t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput) 188 } 189 190 assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput) 191 hook.Reset() 192 } 193 } 194 } 195 196 func TestStreaming(t *testing.T) { 197 if runtime.GOOS == "windows" { 198 t.Skip("Skipping test on windows") 199 } 200 201 tests := []struct { 202 config string 203 expectedErr string 204 expectedOutput string 205 expectedLines int 206 logLevel log.Level 207 }{ 208 { 209 config: ` 210 source: journalctl 211 mode: cat 212 journalctl_filter: 213 - _SYSTEMD_UNIT=ssh.service`, 214 expectedErr: "", 215 expectedOutput: "", 216 logLevel: log.WarnLevel, 217 expectedLines: 14, 218 }, 219 } 220 for _, ts := range tests { 221 var ( 222 logger *log.Logger 223 subLogger *log.Entry 224 hook *test.Hook 225 ) 226 227 if ts.expectedOutput != "" { 228 logger, hook = test.NewNullLogger() 229 logger.SetLevel(ts.logLevel) 230 subLogger = logger.WithFields(log.Fields{ 231 "type": "journalctl", 232 }) 233 } else { 234 subLogger = log.WithFields(log.Fields{ 235 "type": "journalctl", 236 }) 237 } 238 239 tomb := tomb.Tomb{} 240 out := make(chan types.Event) 241 j := JournalCtlSource{} 242 243 err := j.Configure([]byte(ts.config), subLogger, configuration.METRICS_NONE) 244 if err != nil { 245 t.Fatalf("Unexpected error : %s", err) 246 } 247 248 actualLines := 0 249 250 if ts.expectedLines != 0 { 251 go func() { 252 READLOOP: 253 for { 254 select { 255 case <-out: 256 actualLines++ 257 case <-time.After(1 * time.Second): 258 break READLOOP 259 } 260 } 261 }() 262 } 263 264 err = j.StreamingAcquisition(out, &tomb) 265 cstest.AssertErrorContains(t, err, ts.expectedErr) 266 267 if err != nil { 268 continue 269 } 270 271 if ts.expectedLines != 0 { 272 time.Sleep(1 * time.Second) 273 assert.Equal(t, ts.expectedLines, actualLines) 274 } 275 276 tomb.Kill(nil) 277 tomb.Wait() 278 279 output, _ := exec.Command("pgrep", "-x", "journalctl").CombinedOutput() 280 if string(output) != "" { 281 t.Fatalf("Found a journalctl process after killing the tomb !") 282 } 283 284 if ts.expectedOutput != "" { 285 if hook.LastEntry() == nil { 286 t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput) 287 } 288 289 assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput) 290 hook.Reset() 291 } 292 } 293 } 294 295 func TestMain(m *testing.M) { 296 if os.Getenv("USE_SYSTEM_JOURNALCTL") == "" { 297 currentDir, _ := os.Getwd() 298 fullPath := filepath.Join(currentDir, "test_files") 299 os.Setenv("PATH", fullPath+":"+os.Getenv("PATH")) 300 } 301 302 os.Exit(m.Run()) 303 }