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  }