|
@@ -0,0 +1,1017 @@
|
|
|
|
+package setup_test
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "fmt"
|
|
|
|
+ "os"
|
|
|
|
+ "os/exec"
|
|
|
|
+ "runtime"
|
|
|
|
+ "testing"
|
|
|
|
+
|
|
|
|
+ "github.com/lithammer/dedent"
|
|
|
|
+ "github.com/stretchr/testify/require"
|
|
|
|
+
|
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/cstest"
|
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/setup"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+//nolint:dupword
|
|
|
|
+var fakeSystemctlOutput = `UNIT FILE STATE VENDOR PRESET
|
|
|
|
+crowdsec-setup-detect.service enabled enabled
|
|
|
|
+apache2.service enabled enabled
|
|
|
|
+apparmor.service enabled enabled
|
|
|
|
+apport.service enabled enabled
|
|
|
|
+atop.service enabled enabled
|
|
|
|
+atopacct.service enabled enabled
|
|
|
|
+finalrd.service enabled enabled
|
|
|
|
+fwupd-refresh.service enabled enabled
|
|
|
|
+fwupd.service enabled enabled
|
|
|
|
+
|
|
|
|
+9 unit files listed.`
|
|
|
|
+
|
|
|
|
+func fakeExecCommandNotFound(command string, args ...string) *exec.Cmd {
|
|
|
|
+ cs := []string{"-test.run=TestSetupHelperProcess", "--", command}
|
|
|
|
+ cs = append(cs, args...)
|
|
|
|
+ cmd := exec.Command("this-command-does-not-exist", cs...)
|
|
|
|
+ cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
|
|
|
+
|
|
|
|
+ return cmd
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func fakeExecCommand(command string, args ...string) *exec.Cmd {
|
|
|
|
+ cs := []string{"-test.run=TestSetupHelperProcess", "--", command}
|
|
|
|
+ cs = append(cs, args...)
|
|
|
|
+ //nolint:gosec
|
|
|
|
+ cmd := exec.Command(os.Args[0], cs...)
|
|
|
|
+ cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
|
|
|
+
|
|
|
|
+ return cmd
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestSetupHelperProcess(t *testing.T) {
|
|
|
|
+ if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fmt.Fprint(os.Stdout, fakeSystemctlOutput)
|
|
|
|
+ os.Exit(0)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func tempYAML(t *testing.T, content string) string {
|
|
|
|
+ t.Helper()
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ file, err := os.CreateTemp("", "")
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ _, err = file.WriteString(dedent.Dedent(content))
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ err = file.Close()
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ return file.Name()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestPathExists(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+
|
|
|
|
+ type test struct {
|
|
|
|
+ path string
|
|
|
|
+ expected bool
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tests := []test{
|
|
|
|
+ {"/this-should-not-exist", false},
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
+ tests = append(tests, test{`C:\`, true})
|
|
|
|
+ } else {
|
|
|
|
+ tests = append(tests, test{"/tmp", true})
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ env := setup.NewExprEnvironment(setup.DetectOptions{}, setup.ExprOS{})
|
|
|
|
+
|
|
|
|
+ t.Run(tc.path, func(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+ actual := env.PathExists(tc.path)
|
|
|
|
+ require.Equal(t, tc.expected, actual)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestVersionCheck(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+
|
|
|
|
+ tests := []struct {
|
|
|
|
+ version string
|
|
|
|
+ constraint string
|
|
|
|
+ expected bool
|
|
|
|
+ expectedErr string
|
|
|
|
+ }{
|
|
|
|
+ {"1", "=1", true, ""},
|
|
|
|
+ {"1", "!=1", false, ""},
|
|
|
|
+ {"1", "<=1", true, ""},
|
|
|
|
+ {"1", ">1", false, ""},
|
|
|
|
+ {"1", ">=1", true, ""},
|
|
|
|
+ {"1.0", "<1.0", false, ""},
|
|
|
|
+ {"1", "<1", true, ""}, // XXX why?
|
|
|
|
+ {"1.3.5", "1.3", false, ""}, // XXX ok?
|
|
|
|
+ {"1.0", "<1.0", false, ""},
|
|
|
|
+ {"1.0", "<=1.0", true, ""},
|
|
|
|
+ {"2", ">1, <3", true, ""},
|
|
|
|
+ {"2", "<=2, >=2.2", false, ""},
|
|
|
|
+ {"2.3", "~2", true, ""},
|
|
|
|
+ {"2.3", "=2", true, ""},
|
|
|
|
+ {"1.1.1", "=1.1", false, ""},
|
|
|
|
+ {"1.1.1", "1.1", false, ""},
|
|
|
|
+ {"1.1", "!=1.1.1", true, ""},
|
|
|
|
+ {"1.1", "~1.1.1", false, ""},
|
|
|
|
+ {"1.1.1", "~1.1", true, ""},
|
|
|
|
+ {"1.1.3", "~1.1", true, ""},
|
|
|
|
+ {"19.04", "<19.10", true, ""},
|
|
|
|
+ {"19.04", ">=19.10", false, ""},
|
|
|
|
+ {"19.04", "=19.4", true, ""},
|
|
|
|
+ {"19.04", "~19.4", true, ""},
|
|
|
|
+ {"1.2.3", "~1.2", true, ""},
|
|
|
|
+ {"1.2.3", "!=1.2", true, ""},
|
|
|
|
+ {"1.2.3", "1.1.1 - 1.3.4", true, ""},
|
|
|
|
+ {"1.3.5", "1.1.1 - 1.3.4", false, ""},
|
|
|
|
+ {"1.3.5", "=1", true, ""},
|
|
|
|
+ {"1.3.5", "1", true, ""},
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ e := setup.ExprOS{RawVersion: tc.version}
|
|
|
|
+
|
|
|
|
+ t.Run(fmt.Sprintf("Check(%s,%s)", tc.version, tc.constraint), func(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+ actual, err := e.VersionCheck(tc.constraint)
|
|
|
|
+ cstest.RequireErrorContains(t, err, tc.expectedErr)
|
|
|
|
+ require.Equal(t, tc.expected, actual)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// This is not required for Masterminds/semver
|
|
|
|
+/*
|
|
|
|
+func TestNormalizeVersion(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+
|
|
|
|
+ tests := []struct {
|
|
|
|
+ version string
|
|
|
|
+ expected string
|
|
|
|
+ }{
|
|
|
|
+ {"0", "0"},
|
|
|
|
+ {"2", "2"},
|
|
|
|
+ {"3.14", "3.14"},
|
|
|
|
+ {"1.0", "1.0"},
|
|
|
|
+ {"18.04", "18.4"},
|
|
|
|
+ {"0.0.0", "0.0.0"},
|
|
|
|
+ {"18.04.0", "18.4.0"},
|
|
|
|
+ {"18.0004.0", "18.4.0"},
|
|
|
|
+ {"21.04.2", "21.4.2"},
|
|
|
|
+ {"050", "50"},
|
|
|
|
+ {"trololo", "trololo"},
|
|
|
|
+ {"0001.002.03", "1.2.3"},
|
|
|
|
+ {"0001.002.03-trololo", "0001.002.03-trololo"},
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ t.Run(tc.version, func(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+ actual := setup.NormalizeVersion(tc.version)
|
|
|
|
+ require.Equal(t, tc.expected, actual)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+*/
|
|
|
|
+
|
|
|
|
+func TestListSupported(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+
|
|
|
|
+ tests := []struct {
|
|
|
|
+ name string
|
|
|
|
+ yml string
|
|
|
|
+ expected []string
|
|
|
|
+ expectedErr string
|
|
|
|
+ }{
|
|
|
|
+ {
|
|
|
|
+ "list configured services",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foo:
|
|
|
|
+ bar:
|
|
|
|
+ baz:
|
|
|
|
+ `,
|
|
|
|
+ []string{"foo", "bar", "baz"},
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "invalid yaml: blahblah",
|
|
|
|
+ "blahblah",
|
|
|
|
+ nil,
|
|
|
|
+ "yaml: unmarshal errors:",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "invalid yaml: tabs are not allowed",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foos:
|
|
|
|
+ `,
|
|
|
|
+ nil,
|
|
|
|
+ "yaml: line 4: found character that cannot start any token",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "invalid yaml: no version",
|
|
|
|
+ "{}",
|
|
|
|
+ nil,
|
|
|
|
+ "missing version tag (must be 1.0)",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "invalid yaml: bad version",
|
|
|
|
+ "version: 2.0",
|
|
|
|
+ nil,
|
|
|
|
+ "unsupported version tag '2.0' (must be 1.0)",
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+ f := tempYAML(t, tc.yml)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+ supported, err := setup.ListSupported(f)
|
|
|
|
+ cstest.RequireErrorContains(t, err, tc.expectedErr)
|
|
|
|
+ require.ElementsMatch(t, tc.expected, supported)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestApplyRules(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+ require := require.New(t)
|
|
|
|
+
|
|
|
|
+ tests := []struct {
|
|
|
|
+ name string
|
|
|
|
+ rules []string
|
|
|
|
+ expectedOk bool
|
|
|
|
+ expectedErr string
|
|
|
|
+ }{
|
|
|
|
+ {
|
|
|
|
+ "empty list is always true", // XXX or false?
|
|
|
|
+ []string{},
|
|
|
|
+ true,
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "simple true expression",
|
|
|
|
+ []string{"1+1==2"},
|
|
|
|
+ true,
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "simple false expression",
|
|
|
|
+ []string{"2+2==5"},
|
|
|
|
+ false,
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "all expressions are true",
|
|
|
|
+ []string{"1+2==3", "1!=2"},
|
|
|
|
+ true,
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "all expressions must be true",
|
|
|
|
+ []string{"true", "1==3", "1!=2"},
|
|
|
|
+ false,
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "each expression must be a boolan",
|
|
|
|
+ []string{"true", "\"notabool\""},
|
|
|
|
+ false,
|
|
|
|
+ "rule '\"notabool\"': type must be a boolean",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ // we keep evaluating expressions to ensure that the
|
|
|
|
+ // file is formally correct, even if it can some time.
|
|
|
|
+ "each expression must be a boolan (no short circuit)",
|
|
|
|
+ []string{"false", "3"},
|
|
|
|
+ false,
|
|
|
|
+ "rule '3': type must be a boolean",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "unknown variable",
|
|
|
|
+ []string{"false", "doesnotexist"},
|
|
|
|
+ false,
|
|
|
|
+ "rule 'doesnotexist': cannot fetch doesnotexist from",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "unknown expression",
|
|
|
|
+ []string{"false", "doesnotexist()"},
|
|
|
|
+ false,
|
|
|
|
+ "rule 'doesnotexist()': cannot get \"doesnotexist\" from",
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ env := setup.ExprEnvironment{}
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
+ t.Parallel()
|
|
|
|
+ svc := setup.Service{When: tc.rules}
|
|
|
|
+ _, actualOk, err := setup.ApplyRules(svc, env) //nolint:typecheck,nolintlint // exported only for tests
|
|
|
|
+ cstest.RequireErrorContains(t, err, tc.expectedErr)
|
|
|
|
+ require.Equal(tc.expectedOk, actualOk)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// XXX TODO: TestApplyRules with journalctl default
|
|
|
|
+
|
|
|
|
+func TestUnitFound(t *testing.T) {
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ env := setup.NewExprEnvironment(setup.DetectOptions{}, setup.ExprOS{})
|
|
|
|
+
|
|
|
|
+ installed, err := env.UnitFound("crowdsec-setup-detect.service")
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ require.Equal(true, installed)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO apply rules to filter a list of Service structs
|
|
|
|
+// func testFilterWithRules(t *testing.T) {
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+func TestDetectSimpleRule(t *testing.T) {
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ f := tempYAML(t, `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ good:
|
|
|
|
+ when:
|
|
|
|
+ - true
|
|
|
|
+ bad:
|
|
|
|
+ when:
|
|
|
|
+ - false
|
|
|
|
+ ugly:
|
|
|
|
+ `)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(f, setup.DetectOptions{})
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ expected := []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "good"},
|
|
|
|
+ {DetectedService: "ugly"},
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ require.ElementsMatch(expected, detected.Setup)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestDetectUnitError(t *testing.T) {
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
+ t.Skip("skipping on windows")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommandNotFound
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ tests := []struct {
|
|
|
|
+ name string
|
|
|
|
+ config string
|
|
|
|
+ expected setup.Setup
|
|
|
|
+ expectedErr string
|
|
|
|
+ }{
|
|
|
|
+ {
|
|
|
|
+ "error is reported if systemctl does not exist",
|
|
|
|
+ `
|
|
|
|
+version: 1.0
|
|
|
|
+detect:
|
|
|
|
+ wizard:
|
|
|
|
+ when:
|
|
|
|
+ - UnitFound("crowdsec-setup-detect.service")`,
|
|
|
|
+ setup.Setup{[]setup.ServiceSetup{}},
|
|
|
|
+ `while looking for service wizard: rule 'UnitFound("crowdsec-setup-detect.service")': ` +
|
|
|
|
+ `running systemctl: exec: "this-command-does-not-exist": executable file not found in $PATH`,
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
+ f := tempYAML(t, tc.config)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(f, setup.DetectOptions{})
|
|
|
|
+ cstest.RequireErrorContains(t, err, tc.expectedErr)
|
|
|
|
+ require.Equal(tc.expected, detected)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestDetectUnit(t *testing.T) {
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ tests := []struct {
|
|
|
|
+ name string
|
|
|
|
+ config string
|
|
|
|
+ expected setup.Setup
|
|
|
|
+ expectedErr string
|
|
|
|
+ }{
|
|
|
|
+ // {
|
|
|
|
+ // "detect a single unit, with default log filter",
|
|
|
|
+ // `
|
|
|
|
+ // version: 1.0
|
|
|
|
+ // detect:
|
|
|
|
+ // wizard:
|
|
|
|
+ // when:
|
|
|
|
+ // - UnitFound("crowdsec-setup-detect.service")
|
|
|
|
+ // datasource:
|
|
|
|
+ // labels:
|
|
|
|
+ // type: syslog
|
|
|
|
+ // sorcerer:
|
|
|
|
+ // when:
|
|
|
|
+ // - UnitFound("sorcerer.service")`,
|
|
|
|
+ // setup.Setup{
|
|
|
|
+ // Setup: []setup.ServiceSetup{
|
|
|
|
+ // {
|
|
|
|
+ // DetectedService: "wizard",
|
|
|
|
+ // DataSource: setup.DataSourceItem{
|
|
|
|
+ // "Labels": map[string]string{"type": "syslog"},
|
|
|
|
+ // "JournalCTLFilter": []string{"_SYSTEMD_UNIT=crowdsec-setup-detect.service"},
|
|
|
|
+ // },
|
|
|
|
+ // },
|
|
|
|
+ // },
|
|
|
|
+ // },
|
|
|
|
+ // "",
|
|
|
|
+ // },
|
|
|
|
+ // {
|
|
|
|
+ // "detect a single unit, but type label is missing",
|
|
|
|
+ // `
|
|
|
|
+ // version: 1.0
|
|
|
|
+ // detect:
|
|
|
|
+ // wizard:
|
|
|
|
+ // when:
|
|
|
|
+ // - UnitFound("crowdsec-setup-detect.service")`,
|
|
|
|
+ // setup.Setup{},
|
|
|
|
+ // "missing type label for service wizard",
|
|
|
|
+ // },
|
|
|
|
+ {
|
|
|
|
+ "detect unit and pick up acquisistion filter",
|
|
|
|
+ `
|
|
|
|
+version: 1.0
|
|
|
|
+detect:
|
|
|
|
+ wizard:
|
|
|
|
+ when:
|
|
|
|
+ - UnitFound("crowdsec-setup-detect.service")
|
|
|
|
+ datasource:
|
|
|
|
+ source: journalctl
|
|
|
|
+ labels:
|
|
|
|
+ type: syslog
|
|
|
|
+ journalctl_filter:
|
|
|
|
+ - _MY_CUSTOM_FILTER=something`,
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {
|
|
|
|
+ DetectedService: "wizard",
|
|
|
|
+ DataSource: setup.DataSourceItem{
|
|
|
|
+ // XXX this should not be DataSourceItem ??
|
|
|
|
+ "source": "journalctl",
|
|
|
|
+ "labels": setup.DataSourceItem{"type": "syslog"},
|
|
|
|
+ "journalctl_filter": []interface{}{"_MY_CUSTOM_FILTER=something"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
+ f := tempYAML(t, tc.config)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(f, setup.DetectOptions{})
|
|
|
|
+ cstest.RequireErrorContains(t, err, tc.expectedErr)
|
|
|
|
+ require.Equal(tc.expected, detected)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestDetectForcedUnit(t *testing.T) {
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ f := tempYAML(t, `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ wizard:
|
|
|
|
+ when:
|
|
|
|
+ - UnitFound("crowdsec-setup-forced.service")
|
|
|
|
+ datasource:
|
|
|
|
+ source: journalctl
|
|
|
|
+ labels:
|
|
|
|
+ type: syslog
|
|
|
|
+ journalctl_filter:
|
|
|
|
+ - _SYSTEMD_UNIT=crowdsec-setup-forced.service
|
|
|
|
+ `)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(f, setup.DetectOptions{ForcedUnits: []string{"crowdsec-setup-forced.service"}})
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ expected := setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {
|
|
|
|
+ DetectedService: "wizard",
|
|
|
|
+ DataSource: setup.DataSourceItem{
|
|
|
|
+ "source": "journalctl",
|
|
|
|
+ "labels": setup.DataSourceItem{"type": "syslog"},
|
|
|
|
+ "journalctl_filter": []interface{}{"_SYSTEMD_UNIT=crowdsec-setup-forced.service"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+ require.Equal(expected, detected)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestDetectForcedProcess(t *testing.T) {
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
+ t.Skip("skipping on windows")
|
|
|
|
+ // while looking for service wizard: rule 'ProcessRunning("foobar")': while looking up running processes: could not get Name: A device attached to the system is not functioning.
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ f := tempYAML(t, `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ wizard:
|
|
|
|
+ when:
|
|
|
|
+ - ProcessRunning("foobar")
|
|
|
|
+ `)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}})
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ expected := setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "wizard"},
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+ require.Equal(expected, detected)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestDetectSkipService(t *testing.T) {
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
+ t.Skip("skipping on windows")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ f := tempYAML(t, `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ wizard:
|
|
|
|
+ when:
|
|
|
|
+ - ProcessRunning("foobar")
|
|
|
|
+ `)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}, SkipServices: []string{"wizard"}})
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ expected := setup.Setup{[]setup.ServiceSetup{}}
|
|
|
|
+ require.Equal(expected, detected)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestDetectForcedOS(t *testing.T) {
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ type test struct {
|
|
|
|
+ name string
|
|
|
|
+ config string
|
|
|
|
+ forced setup.ExprOS
|
|
|
|
+ expected setup.Setup
|
|
|
|
+ expectedErr string
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tests := []test{
|
|
|
|
+ {
|
|
|
|
+ "detect OS - force linux",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.Family == "linux"`,
|
|
|
|
+ setup.ExprOS{Family: "linux"},
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "linux"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - force windows",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ windows:
|
|
|
|
+ when:
|
|
|
|
+ - OS.Family == "windows"`,
|
|
|
|
+ setup.ExprOS{Family: "windows"},
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "windows"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu (no match)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.Family == "linux" && OS.ID == "ubuntu"`,
|
|
|
|
+ setup.ExprOS{Family: "linux"},
|
|
|
|
+ setup.Setup{[]setup.ServiceSetup{}},
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu (match)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.Family == "linux" && OS.ID == "ubuntu"`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu"},
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "linux"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu (match with version)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.Family == "linux" && OS.ID == "ubuntu" && OS.VersionCheck("19.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "19.04"},
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "linux"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu >= 20.04 (no match: no version detected)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux"},
|
|
|
|
+ setup.Setup{[]setup.ServiceSetup{}},
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu >= 20.04 (no match: version is lower)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "19.10"},
|
|
|
|
+ setup.Setup{[]setup.ServiceSetup{}},
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu >= 20.04 (match: same version)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "20.04"},
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "linux"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu >= 20.04 (match: version is higher)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "22.04"},
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "linux"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu < 20.04 (no match: no version detected)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu" && OS.VersionCheck("<20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux"},
|
|
|
|
+ setup.Setup{[]setup.ServiceSetup{}},
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu < 20.04 (no match: version is higher)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu" && OS.VersionCheck("<20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "20.10"},
|
|
|
|
+ setup.Setup{[]setup.ServiceSetup{}},
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu < 20.04 (no match: same version)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu" && OS.VersionCheck("<20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "20.04"},
|
|
|
|
+ setup.Setup{[]setup.ServiceSetup{}},
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "detect OS - ubuntu < 20.04 (match: version is lower)",
|
|
|
|
+ `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ linux:
|
|
|
|
+ when:
|
|
|
|
+ - OS.ID == "ubuntu"
|
|
|
|
+ - OS.VersionCheck("<20.04")`,
|
|
|
|
+ setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "19.10"},
|
|
|
|
+ setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {DetectedService: "linux"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ "",
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
+ f := tempYAML(t, tc.config)
|
|
|
|
+ defer os.Remove(f)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(f, setup.DetectOptions{ForcedOS: tc.forced})
|
|
|
|
+ cstest.RequireErrorContains(t, err, tc.expectedErr)
|
|
|
|
+ require.Equal(tc.expected, detected)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestDetectDatasourceValidation(t *testing.T) {
|
|
|
|
+ // It could be a good idea to test UnmarshalConfig() separately in addition
|
|
|
|
+ // to Configure(), in each datasource. For now, we test these here.
|
|
|
|
+
|
|
|
|
+ require := require.New(t)
|
|
|
|
+ setup.ExecCommand = fakeExecCommand
|
|
|
|
+
|
|
|
|
+ defer func() { setup.ExecCommand = exec.Command }()
|
|
|
|
+
|
|
|
|
+ type test struct {
|
|
|
|
+ name string
|
|
|
|
+ config string
|
|
|
|
+ expected setup.Setup
|
|
|
|
+ expectedErr string
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tests := []test{
|
|
|
|
+ {
|
|
|
|
+ name: "source is empty",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ wizard:
|
|
|
|
+ datasource:
|
|
|
|
+ labels:
|
|
|
|
+ type: something`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for wizard: source is empty",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source is unknown",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: wombat`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: unknown source 'wombat'",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source is misplaced",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: file`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "while parsing {{.DetectYaml}}: yaml: unmarshal errors:\n line 6: field source not found in type setup.Service",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source is mismatched",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: journalctl
|
|
|
|
+ filename: /path/to/file.log`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: cannot parse JournalCtlSource configuration: yaml: unmarshal errors:\n line 1: field filename not found in type journalctlacquisition.JournalCtlConfiguration",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source file: required fields",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: file`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: no filename or filenames configuration provided",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source journalctl: required fields",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: journalctl`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: journalctl_filter is required",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source cloudwatch: required fields",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: cloudwatch`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: group_name is mandatory for CloudwatchSource",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source syslog: all fields are optional",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: syslog`,
|
|
|
|
+ expected: setup.Setup{
|
|
|
|
+ Setup: []setup.ServiceSetup{
|
|
|
|
+ {
|
|
|
|
+ DetectedService:"foobar",
|
|
|
|
+ DataSource: setup.DataSourceItem{"source":"syslog"},
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ }, {
|
|
|
|
+ name: "source docker: required fields",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: docker`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: no containers names or containers ID configuration provided",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source kinesis: required fields (enhanced fanout=false)",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: kinesis`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: stream_name is mandatory when use_enhanced_fanout is false",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source kinesis: required fields (enhanced fanout=true)",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: kinesis
|
|
|
|
+ use_enhanced_fanout: true`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: stream_arn is mandatory when use_enhanced_fanout is true",
|
|
|
|
+ }, {
|
|
|
|
+ name: "source kafka: required fields",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: kafka`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: cannot create a kafka reader with an empty list of broker addresses",
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
|
+ tests = append(tests, test{
|
|
|
|
+ name: "source wineventlog: required fields",
|
|
|
|
+ config: `
|
|
|
|
+ version: 1.0
|
|
|
|
+ detect:
|
|
|
|
+ foobar:
|
|
|
|
+ datasource:
|
|
|
|
+ source: wineventlog`,
|
|
|
|
+ expected: setup.Setup{Setup:[]setup.ServiceSetup{}},
|
|
|
|
+ expectedErr: "invalid datasource for foobar: event_channel or xpath_query must be set",
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ tc := tc
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
+ detectYaml := tempYAML(t, tc.config)
|
|
|
|
+ defer os.Remove(detectYaml)
|
|
|
|
+
|
|
|
|
+ data := map[string]string{
|
|
|
|
+ "DetectYaml": detectYaml,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ expectedErr, err := cstest.Interpolate(tc.expectedErr, data)
|
|
|
|
+ require.NoError(err)
|
|
|
|
+
|
|
|
|
+ detected, err := setup.Detect(detectYaml, setup.DetectOptions{})
|
|
|
|
+ cstest.RequireErrorContains(t, err, expectedErr)
|
|
|
|
+ require.Equal(tc.expected, detected)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|