file_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. package fileacquisition
  2. import (
  3. "fmt"
  4. "os"
  5. "testing"
  6. "time"
  7. "github.com/crowdsecurity/crowdsec/pkg/types"
  8. log "github.com/sirupsen/logrus"
  9. "github.com/sirupsen/logrus/hooks/test"
  10. "github.com/stretchr/testify/assert"
  11. "gopkg.in/tomb.v2"
  12. )
  13. func TestBadConfiguration(t *testing.T) {
  14. tests := []struct {
  15. config string
  16. expectedErr string
  17. }{
  18. {
  19. config: `foobar: asd.log`,
  20. expectedErr: "line 1: field foobar not found in type fileacquisition.FileConfiguration",
  21. },
  22. {
  23. config: `mode: tail`,
  24. expectedErr: "no filename or filenames configuration provided",
  25. },
  26. {
  27. config: `filename: "[asd-.log"`,
  28. expectedErr: "Glob failure: syntax error in pattern",
  29. },
  30. }
  31. subLogger := log.WithFields(log.Fields{
  32. "type": "file",
  33. })
  34. for _, test := range tests {
  35. f := FileSource{}
  36. err := f.Configure([]byte(test.config), subLogger)
  37. assert.Contains(t, err.Error(), test.expectedErr)
  38. }
  39. }
  40. func TestConfigureDSN(t *testing.T) {
  41. tests := []struct {
  42. dsn string
  43. expectedErr string
  44. }{
  45. {
  46. dsn: "asd://",
  47. expectedErr: "invalid DSN asd:// for file source, must start with file://",
  48. },
  49. {
  50. dsn: "file://",
  51. expectedErr: "empty file:// DSN",
  52. },
  53. {
  54. dsn: "file:///etc/passwd?log_level=warn",
  55. expectedErr: "",
  56. },
  57. {
  58. dsn: "file:///etc/passwd?log_level=foobar",
  59. expectedErr: "unknown level foobar: not a valid logrus Level:",
  60. },
  61. }
  62. subLogger := log.WithFields(log.Fields{
  63. "type": "file",
  64. })
  65. for _, test := range tests {
  66. f := FileSource{}
  67. err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger)
  68. if test.expectedErr != "" {
  69. assert.Contains(t, err.Error(), test.expectedErr)
  70. } else {
  71. assert.Equal(t, err, nil)
  72. }
  73. }
  74. }
  75. func TestOneShot(t *testing.T) {
  76. tests := []struct {
  77. config string
  78. expectedErr string
  79. expectedOutput string
  80. expectedLines int
  81. logLevel log.Level
  82. setup func()
  83. afterConfigure func()
  84. teardown func()
  85. }{
  86. {
  87. config: `
  88. mode: cat
  89. filename: /etc/shadow`,
  90. expectedErr: "failed opening /etc/shadow: open /etc/shadow: permission denied",
  91. expectedOutput: "",
  92. logLevel: log.WarnLevel,
  93. expectedLines: 0,
  94. },
  95. {
  96. config: `
  97. mode: cat
  98. filename: /`,
  99. expectedErr: "",
  100. expectedOutput: "/ is a directory, ignoring it",
  101. logLevel: log.WarnLevel,
  102. expectedLines: 0,
  103. },
  104. {
  105. config: `
  106. mode: cat
  107. filename: "[*-.log"`,
  108. expectedErr: "Glob failure: syntax error in pattern",
  109. expectedOutput: "",
  110. logLevel: log.WarnLevel,
  111. expectedLines: 0,
  112. },
  113. {
  114. config: `
  115. mode: cat
  116. filename: /do/not/exist`,
  117. expectedErr: "",
  118. expectedOutput: "No matching files for pattern /do/not/exist",
  119. logLevel: log.WarnLevel,
  120. expectedLines: 0,
  121. },
  122. {
  123. config: `
  124. mode: cat
  125. filename: test_files/test.log`,
  126. expectedErr: "",
  127. expectedOutput: "",
  128. expectedLines: 5,
  129. logLevel: log.WarnLevel,
  130. },
  131. {
  132. config: `
  133. mode: cat
  134. filename: test_files/test.log.gz`,
  135. expectedErr: "",
  136. expectedOutput: "",
  137. expectedLines: 5,
  138. logLevel: log.WarnLevel,
  139. },
  140. {
  141. config: `
  142. mode: cat
  143. filename: test_files/bad.gz`,
  144. expectedErr: "failed to read gz test_files/bad.gz: unexpected EOF",
  145. expectedOutput: "",
  146. expectedLines: 0,
  147. logLevel: log.WarnLevel,
  148. },
  149. {
  150. config: `
  151. mode: cat
  152. filename: test_files/test_delete.log`,
  153. setup: func() {
  154. os.Create("test_files/test_delete.log")
  155. },
  156. afterConfigure: func() {
  157. os.Remove("test_files/test_delete.log")
  158. },
  159. expectedErr: "could not stat file test_files/test_delete.log : stat test_files/test_delete.log: no such file or directory",
  160. },
  161. }
  162. for _, ts := range tests {
  163. logger, hook := test.NewNullLogger()
  164. logger.SetLevel(ts.logLevel)
  165. subLogger := logger.WithFields(log.Fields{
  166. "type": "file",
  167. })
  168. tomb := tomb.Tomb{}
  169. out := make(chan types.Event)
  170. f := FileSource{}
  171. if ts.setup != nil {
  172. ts.setup()
  173. }
  174. err := f.Configure([]byte(ts.config), subLogger)
  175. if err != nil && ts.expectedErr != "" {
  176. assert.Contains(t, err.Error(), ts.expectedErr)
  177. continue
  178. } else if err != nil && ts.expectedErr == "" {
  179. t.Fatalf("Unexpected error : %s", err)
  180. }
  181. if ts.afterConfigure != nil {
  182. ts.afterConfigure()
  183. }
  184. actualLines := 0
  185. if ts.expectedLines != 0 {
  186. go func() {
  187. READLOOP:
  188. for {
  189. select {
  190. case <-out:
  191. actualLines++
  192. case <-time.After(1 * time.Second):
  193. break READLOOP
  194. }
  195. }
  196. }()
  197. }
  198. err = f.OneShotAcquisition(out, &tomb)
  199. if ts.expectedLines != 0 {
  200. assert.Equal(t, actualLines, ts.expectedLines)
  201. }
  202. if ts.expectedErr != "" {
  203. if err == nil {
  204. t.Fatalf("Expected error but got nothing ! %+v", ts)
  205. }
  206. assert.Contains(t, err.Error(), ts.expectedErr)
  207. }
  208. if ts.expectedOutput != "" {
  209. assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
  210. hook.Reset()
  211. }
  212. if ts.teardown != nil {
  213. ts.teardown()
  214. }
  215. }
  216. }
  217. func TestLiveAcquisition(t *testing.T) {
  218. tests := []struct {
  219. config string
  220. expectedErr string
  221. expectedOutput string
  222. expectedLines int
  223. logLevel log.Level
  224. setup func()
  225. afterConfigure func()
  226. teardown func()
  227. }{
  228. {
  229. config: `
  230. mode: tail
  231. filename: /etc/shadow`,
  232. expectedErr: "",
  233. expectedOutput: "unable to read /etc/shadow : open /etc/shadow: permission denied",
  234. logLevel: log.InfoLevel,
  235. expectedLines: 0,
  236. },
  237. {
  238. config: `
  239. mode: tail
  240. filename: /`,
  241. expectedErr: "",
  242. expectedOutput: "/ is a directory, ignoring it",
  243. logLevel: log.WarnLevel,
  244. expectedLines: 0,
  245. },
  246. {
  247. config: `
  248. mode: tail
  249. filename: /do/not/exist`,
  250. expectedErr: "",
  251. expectedOutput: "No matching files for pattern /do/not/exist",
  252. logLevel: log.WarnLevel,
  253. expectedLines: 0,
  254. },
  255. {
  256. config: `
  257. mode: tail
  258. filenames:
  259. - test_files/*.log
  260. force_inotify: true`,
  261. expectedErr: "",
  262. expectedOutput: "",
  263. expectedLines: 5,
  264. logLevel: log.DebugLevel,
  265. },
  266. {
  267. config: `
  268. mode: tail
  269. filenames:
  270. - test_files/*.log
  271. force_inotify: true`,
  272. expectedErr: "",
  273. expectedOutput: "",
  274. expectedLines: 0,
  275. logLevel: log.DebugLevel,
  276. afterConfigure: func() {
  277. os.Create("test_files/a.log")
  278. os.Remove("test_files/a.log")
  279. },
  280. },
  281. {
  282. config: `
  283. mode: tail
  284. filenames:
  285. - test_files/*.log
  286. force_inotify: true`,
  287. expectedErr: "",
  288. expectedOutput: "",
  289. expectedLines: 5,
  290. logLevel: log.DebugLevel,
  291. afterConfigure: func() {
  292. os.Create("test_files/a.log")
  293. time.Sleep(1 * time.Second)
  294. os.Chmod("test_files/a.log", 0000)
  295. },
  296. teardown: func() {
  297. os.Chmod("test_files/a.log", 0644)
  298. os.Remove("test_files/a.log")
  299. },
  300. },
  301. {
  302. config: `
  303. mode: tail
  304. filenames:
  305. - test_files/*.log
  306. force_inotify: true`,
  307. expectedErr: "",
  308. expectedOutput: "",
  309. expectedLines: 5,
  310. logLevel: log.DebugLevel,
  311. afterConfigure: func() {
  312. os.Mkdir("test_files/pouet/", 0700)
  313. },
  314. teardown: func() {
  315. os.Remove("test_files/pouet/")
  316. },
  317. },
  318. }
  319. for _, ts := range tests {
  320. logger, hook := test.NewNullLogger()
  321. logger.SetLevel(ts.logLevel)
  322. subLogger := logger.WithFields(log.Fields{
  323. "type": "file",
  324. })
  325. tomb := tomb.Tomb{}
  326. out := make(chan types.Event)
  327. f := FileSource{}
  328. if ts.setup != nil {
  329. ts.setup()
  330. }
  331. err := f.Configure([]byte(ts.config), subLogger)
  332. if err != nil {
  333. t.Fatalf("Unexpected error : %s", err)
  334. }
  335. if ts.afterConfigure != nil {
  336. ts.afterConfigure()
  337. }
  338. actualLines := 0
  339. if ts.expectedLines != 0 {
  340. go func() {
  341. READLOOP:
  342. for {
  343. select {
  344. case <-out:
  345. actualLines++
  346. case <-time.After(2 * time.Second):
  347. break READLOOP
  348. }
  349. }
  350. }()
  351. }
  352. err = f.StreamingAcquisition(out, &tomb)
  353. if ts.expectedErr != "" {
  354. if err == nil {
  355. t.Fatalf("Expected error but got nothing ! %+v", ts)
  356. }
  357. assert.Contains(t, err.Error(), ts.expectedErr)
  358. }
  359. if ts.expectedLines != 0 {
  360. fd, err := os.Create("test_files/stream.log")
  361. if err != nil {
  362. t.Fatalf("could not create test file : %s", err)
  363. }
  364. for i := 0; i < 5; i++ {
  365. _, err = fd.WriteString(fmt.Sprintf("%d\n", i))
  366. if err != nil {
  367. t.Fatalf("could not write test file : %s", err)
  368. os.Remove("test_files/stream.log")
  369. }
  370. }
  371. fd.Close()
  372. //we sleep to make sure we detect the new file
  373. time.Sleep(1 * time.Second)
  374. os.Remove("test_files/stream.log")
  375. assert.Equal(t, actualLines, ts.expectedLines)
  376. }
  377. if ts.expectedOutput != "" {
  378. if hook.LastEntry() == nil {
  379. t.Fatalf("expected output %s, but got nothing", ts.expectedOutput)
  380. }
  381. assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
  382. hook.Reset()
  383. }
  384. if ts.teardown != nil {
  385. ts.teardown()
  386. }
  387. tomb.Kill(nil)
  388. }
  389. }