bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/pkg/acquisition/journalctl_reader_test.go (about) 1 package acquisition 2 3 import ( 4 "fmt" 5 "os" 6 "testing" 7 "time" 8 9 "bitbucket.org/Aishee/synsec/pkg/types" 10 log "github.com/sirupsen/logrus" 11 "github.com/stretchr/testify/assert" 12 tomb "gopkg.in/tomb.v2" 13 ) 14 15 /* 16 As we can't decently run journalctl in the CI but we still need to test the command execution aspect : 17 - we create tests 'output only' (cf. TestSimJournalctlCat) that just produce outputs 18 - we run ourselves (os.Args[0]) with specific args to call specific 'output only' tests 19 - and this is how we test the behavior 20 */ 21 22 //14 lines of sshd logs 23 var testjournalctl_output_1 string = `-- Logs begin at Fri 2019-07-26 17:13:13 CEST, end at Mon 2020-11-23 09:17:34 CET. -- 24 Nov 22 11:22:19 zeroed sshd[1480]: Invalid user wqeqwe from 127.0.0.1 port 55818 25 Nov 22 11:22:23 zeroed sshd[1480]: Failed password for invalid user wqeqwe from 127.0.0.1 port 55818 ssh2 26 Nov 22 11:23:22 zeroed sshd[1769]: Invalid user wqeqwe1 from 127.0.0.1 port 55824 27 Nov 22 11:23:24 zeroed sshd[1769]: Disconnecting invalid user wqeqwe1 127.0.0.1 port 55824: Too many authentication failures [preauth] 28 Nov 22 11:23:24 zeroed sshd[1777]: Invalid user wqeqwe2 from 127.0.0.1 port 55826 29 Nov 22 11:23:25 zeroed sshd[1777]: Disconnecting invalid user wqeqwe2 127.0.0.1 port 55826: Too many authentication failures [preauth] 30 Nov 22 11:23:25 zeroed sshd[1780]: Invalid user wqeqwe3 from 127.0.0.1 port 55828 31 Nov 22 11:23:26 zeroed sshd[1780]: Disconnecting invalid user wqeqwe3 127.0.0.1 port 55828: Too many authentication failures [preauth] 32 Nov 22 11:23:26 zeroed sshd[1786]: Invalid user wqeqwe4 from 127.0.0.1 port 55830 33 Nov 22 11:23:27 zeroed sshd[1786]: Failed password for invalid user wqeqwe4 from 127.0.0.1 port 55830 ssh2 34 Nov 22 11:23:27 zeroed sshd[1786]: Disconnecting invalid user wqeqwe4 127.0.0.1 port 55830: Too many authentication failures [preauth] 35 Nov 22 11:23:27 zeroed sshd[1791]: Invalid user wqeqwe5 from 127.0.0.1 port 55834 36 Nov 22 11:23:27 zeroed sshd[1791]: Failed password for invalid user wqeqwe5 from 127.0.0.1 port 55834 ssh2 37 ` 38 39 func TestSimJournalctlCat(t *testing.T) { 40 if os.Getenv("GO_WANT_TEST_OUTPUT") != "1" { 41 return 42 } 43 defer os.Exit(0) 44 fmt.Print(testjournalctl_output_1) 45 } 46 47 func TestSimJournalctlCatError(t *testing.T) { 48 if os.Getenv("GO_WANT_TEST_OUTPUT") != "1" { 49 return 50 } 51 defer os.Exit(0) 52 fmt.Print("this is a single line being produced") 53 log.Warningf("this is an error message") 54 } 55 56 func TestSimJournalctlCatOneLine(t *testing.T) { 57 if os.Getenv("GO_WANT_TEST_OUTPUT") != "1" { 58 return 59 } 60 defer os.Exit(0) 61 fmt.Print("this is a single line being produced") 62 } 63 64 func TestJournaldTail(t *testing.T) { 65 tests := []struct { 66 cfg DataSourceCfg 67 config_error string 68 read_error string 69 tomb_error string 70 lines int 71 }{ 72 { //missing filename(s) 73 cfg: DataSourceCfg{ 74 Mode: TAIL_MODE, 75 }, 76 config_error: "journalctl_filter shouldn't be empty", 77 }, 78 { //bad mode 79 cfg: DataSourceCfg{ 80 Mode: "ratatata", 81 JournalctlFilters: []string{"-test.run=DoesNotExist", "--"}, 82 }, 83 /*here would actually be the journalctl error message on bad args, but you get the point*/ 84 config_error: "unknown mode 'ratatata' for journald source", 85 }, 86 { //wrong arguments 87 cfg: DataSourceCfg{ 88 Mode: TAIL_MODE, 89 JournalctlFilters: []string{"--this-is-bad-option", "--"}, 90 }, 91 /*here would actually be the journalctl error message on bad args, but you get the point*/ 92 tomb_error: "flag provided but not defined: -this-is-bad-option", 93 }, 94 } 95 96 //we're actually using tests to do this, hold my beer and watch this 97 JOURNALD_CMD = os.Args[0] 98 JOURNALD_DEFAULT_TAIL_ARGS = []string{} 99 100 for tidx, test := range tests { 101 journalSrc := new(JournaldSource) 102 err := journalSrc.Configure(test.cfg) 103 if test.config_error != "" { 104 assert.Contains(t, fmt.Sprintf("%s", err), test.config_error) 105 log.Infof("expected config error ok : %s", test.config_error) 106 continue 107 } else { 108 if err != nil { 109 t.Fatalf("%d/%d unexpected config error %s", tidx, len(tests), err) 110 } 111 } 112 113 assert.Equal(t, journalSrc.Mode(), test.cfg.Mode) 114 115 //this tells our fake tests to produce data 116 journalSrc.Cmd.Env = []string{"GO_WANT_TEST_OUTPUT=1"} 117 118 out := make(chan types.Event) 119 tomb := tomb.Tomb{} 120 count := 0 121 122 //start consuming the data before we start the prog, so that chan isn't full 123 go func() { 124 for { 125 select { 126 case <-out: 127 count++ 128 case <-time.After(1 * time.Second): 129 return 130 } 131 } 132 }() 133 134 err = journalSrc.StartReading(out, &tomb) 135 if test.read_error != "" { 136 assert.Contains(t, fmt.Sprintf("%s", err), test.read_error) 137 log.Infof("expected read error ok : %s", test.read_error) 138 continue 139 } else { 140 if err != nil { 141 t.Fatalf("%d/%d unexpected read error %s", tidx, len(tests), err) 142 } 143 } 144 145 time.Sleep(2 * time.Second) 146 log.Printf("now let's check number of lines & errors") 147 if count != test.lines { 148 t.Fatalf("%d/%d expected %d line read, got %d", tidx, len(tests), test.lines, count) 149 } 150 151 if test.tomb_error != "" { 152 assert.Contains(t, fmt.Sprintf("%s", tomb.Err()), test.tomb_error) 153 log.Infof("expected tomb error ok : %s", test.read_error) 154 continue 155 } else { 156 if tomb.Err() != nil { 157 t.Fatalf("%d/%d unexpected tomb error %s", tidx, len(tests), tomb.Err()) 158 } 159 } 160 161 } 162 } 163 164 func TestJournaldSimple(t *testing.T) { 165 JOURNALD_CMD = os.Args[0] 166 JOURNALD_DEFAULT_TAIL_ARGS = []string{} 167 jBaseCfg := DataSourceCfg{ 168 JournalctlFilters: []string{"-test.run=TestSimJournalctlCat", "--"}, 169 Mode: CAT_MODE, 170 } 171 172 journalSrc := new(JournaldSource) 173 err := journalSrc.Configure(jBaseCfg) 174 if err != nil { 175 t.Fatalf("configuring journalctl : %s", err) 176 } 177 journalSrc.Cmd.Env = []string{"GO_WANT_TEST_OUTPUT=1"} 178 179 out := make(chan types.Event) 180 tomb := tomb.Tomb{} 181 count := 0 182 183 //start the reading : it doesn't give hand back before it's done 184 err = journalSrc.StartReading(out, &tomb) 185 if err != nil { 186 t.Fatalf("unexpected read error %s", err) 187 } 188 189 RLOOP: 190 for { 191 select { 192 case <-out: 193 count++ 194 case <-time.After(1 * time.Second): 195 break RLOOP 196 } 197 } 198 //we expect 14 lines to be read 199 assert.Equal(t, 14, count) 200 201 } 202 203 func TestJournalctlKill(t *testing.T) { 204 cfg := DataSourceCfg{ 205 Mode: CAT_MODE, 206 JournalctlFilters: []string{"-test.run=TestSimJournalctlCatOneLine", "--"}, 207 } 208 //we're actually using tests to do this, hold my beer and watch this 209 JOURNALD_CMD = os.Args[0] 210 JOURNALD_DEFAULT_TAIL_ARGS = []string{} 211 212 log.SetLevel(log.TraceLevel) 213 journalSrc := new(JournaldSource) 214 err := journalSrc.Configure(cfg) 215 if err != nil { 216 t.Fatalf("unexpected config error %s", err) 217 } 218 journalSrc.Cmd.Env = []string{"GO_WANT_TEST_OUTPUT=1"} 219 220 out := make(chan types.Event) 221 tb := tomb.Tomb{} 222 223 err = journalSrc.StartReading(out, &tb) 224 if err != nil { 225 t.Fatalf("unexpected read error %s", err) 226 } 227 time.Sleep(1 * time.Second) 228 if tb.Err() != tomb.ErrStillAlive { 229 t.Fatalf("unexpected tomb error %s (should be alive)", tb.Err()) 230 } 231 //kill it :> 232 tb.Kill(nil) 233 time.Sleep(1 * time.Second) 234 if tb.Err() != nil { 235 t.Fatalf("unexpected tomb error %s (should be dead)", tb.Err()) 236 } 237 238 }