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  }