mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 08:34:40 +01:00
327 lines
9.8 KiB
Go
327 lines
9.8 KiB
Go
package rule
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var tmpDir string
|
|
|
|
func TestMain(m *testing.M) {
|
|
tmpDir = "/tmp/ostest_" + randString()
|
|
os.Mkdir(tmpDir, 0777)
|
|
defer os.RemoveAll(tmpDir)
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func TestRuleLoader(t *testing.T) {
|
|
t.Parallel()
|
|
t.Log("Test rules loader")
|
|
|
|
var list []Operator
|
|
dur1s := Duration("1s")
|
|
dummyOper, _ := NewOperator(Simple, false, OpTrue, "", list)
|
|
dummyOper.Compile()
|
|
inMem1sRule := Create("000-xxx-name", "rule description xxx", true, false, false, Allow, dur1s, dummyOper)
|
|
inMemUntilRestartRule := Create("000-aaa-name", "rule description aaa", true, false, false, Allow, Restart, dummyOper)
|
|
|
|
l, err := NewLoader(false)
|
|
if err != nil {
|
|
t.Fail()
|
|
}
|
|
if err = l.Load("/non/existent/path/"); err == nil {
|
|
t.Error("non existent path test: err should not be nil")
|
|
}
|
|
|
|
if err = l.Load("testdata/"); err != nil {
|
|
t.Error("Error loading test rules: ", err)
|
|
}
|
|
// we expect 6 valid rules (2 invalid), loaded from testdata/
|
|
testNumRules(t, l, 6)
|
|
|
|
if err = l.Add(inMem1sRule, false); err != nil {
|
|
t.Error("Error adding temporary rule")
|
|
}
|
|
testNumRules(t, l, 7)
|
|
|
|
// test auto deletion of temporary rule
|
|
time.Sleep(time.Second * 2)
|
|
testNumRules(t, l, 6)
|
|
|
|
if err = l.Add(inMemUntilRestartRule, false); err != nil {
|
|
t.Error("Error adding temporary rule (2)")
|
|
}
|
|
testNumRules(t, l, 7)
|
|
testRulesOrder(t, l)
|
|
testSortRules(t, l)
|
|
testFindMatch(t, l)
|
|
testFindEnabled(t, l)
|
|
testDurationChange(t, l)
|
|
}
|
|
|
|
func TestRuleLoaderInvalidRegexp(t *testing.T) {
|
|
t.Parallel()
|
|
t.Log("Test rules loader: invalid regexp")
|
|
|
|
l, err := NewLoader(true)
|
|
if err != nil {
|
|
t.Fail()
|
|
}
|
|
t.Run("loadRule() from disk test (simple)", func(t *testing.T) {
|
|
if err := l.loadRule("testdata/invalid-regexp.json"); err == nil {
|
|
t.Error("invalid regexp rule loaded: loadRule()")
|
|
}
|
|
})
|
|
|
|
t.Run("loadRule() from disk test (list)", func(t *testing.T) {
|
|
if err := l.loadRule("testdata/invalid-regexp-list.json"); err == nil {
|
|
t.Error("invalid regexp rule loaded: loadRule()")
|
|
}
|
|
})
|
|
|
|
var list []Operator
|
|
dur30m := Duration("30m")
|
|
opListData := `[{"type": "regexp", "operand": "process.path", "sensitive": false, "data": "^(/di(rmngr)$"}, {"type": "simple", "operand": "dest.port", "data": "53", "sensitive": false}]`
|
|
invalidRegexpOp, _ := NewOperator(List, false, OpList, opListData, list)
|
|
invalidRegexpRule := Create("invalid-regexp", "invalid rule description", true, false, false, Allow, dur30m, invalidRegexpOp)
|
|
|
|
t.Run("replaceUserRule() test list", func(t *testing.T) {
|
|
if err := l.replaceUserRule(invalidRegexpRule); err == nil {
|
|
t.Error("invalid regexp rule loaded: replaceUserRule()")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test rules of type operator.list. There're these scenarios:
|
|
// - Enabled rules:
|
|
// * operator Data field is ignored if it contains the list of operators as json string.
|
|
// * the operarots list is expanded as json objecs under "list": []
|
|
// For new rules (> v1.6.3), Data field will be empty.
|
|
//
|
|
// - Disabled rules
|
|
// * (old) the Data field contains the list of operators as json string, and the list of operarots is empty.
|
|
// * Data field empty, and the list of operators expanded.
|
|
// In all cases the list of operators must be loaded.
|
|
func TestRuleLoaderList(t *testing.T) {
|
|
l, err := NewLoader(true)
|
|
if err != nil {
|
|
t.Fail()
|
|
}
|
|
|
|
testRules := map[string]string{
|
|
"rule-with-operator-list": "testdata/rule-operator-list.json",
|
|
"rule-disabled-with-operators-list-as-json-string": "testdata/rule-disabled-operator-list.json",
|
|
"rule-disabled-with-operators-list-expanded": "testdata/rule-disabled-operator-list-expanded.json",
|
|
"rule-with-operator-list-data-empty": "testdata/rule-operator-list-data-empty.json",
|
|
}
|
|
|
|
for name, path := range testRules {
|
|
t.Run(fmt.Sprint("loadRule() ", path), func(t *testing.T) {
|
|
if err := l.loadRule(path); err != nil {
|
|
t.Error(fmt.Sprint("loadRule() ", path, " error:"), err)
|
|
}
|
|
t.Log("Test: List rule:", name, path)
|
|
r, found := l.rules[name]
|
|
if !found {
|
|
t.Error(fmt.Sprint("loadRule() ", path, " not in the list:"), l.rules)
|
|
}
|
|
// Starting from > v1.6.3, after loading a rule of type List, the field Operator.Data is emptied, if the Data contained the list of operators as json.
|
|
if len(r.Operator.List) != 2 {
|
|
t.Error(fmt.Sprint("loadRule() ", path, " operator List not loaded:"), r)
|
|
}
|
|
if r.Operator.List[0].Type != Simple ||
|
|
r.Operator.List[0].Operand != OpProcessPath ||
|
|
r.Operator.List[0].Data != "/usr/bin/telnet" {
|
|
t.Error(fmt.Sprint("loadRule() ", path, " operator List 0 not loaded:"), r)
|
|
}
|
|
if r.Operator.List[1].Type != Simple ||
|
|
r.Operator.List[1].Operand != OpDstPort ||
|
|
r.Operator.List[1].Data != "53" {
|
|
t.Error(fmt.Sprint("loadRule() ", path, " operator List 1 not loaded:"), r)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLiveReload(t *testing.T) {
|
|
t.Parallel()
|
|
t.Log("Test rules loader with live reload")
|
|
l, err := NewLoader(true)
|
|
if err != nil {
|
|
t.Fail()
|
|
}
|
|
if err = Copy("testdata/000-allow-chrome.json", tmpDir+"/000-allow-chrome.json"); err != nil {
|
|
t.Error("Error copying rule into a temp dir")
|
|
}
|
|
if err = Copy("testdata/001-deny-chrome.json", tmpDir+"/001-deny-chrome.json"); err != nil {
|
|
t.Error("Error copying rule into a temp dir")
|
|
}
|
|
if err = l.Load(tmpDir); err != nil {
|
|
t.Error("Error loading test rules: ", err)
|
|
}
|
|
//wait for watcher to activate
|
|
time.Sleep(time.Second)
|
|
if err = Copy("testdata/live_reload/test-live-reload-remove.json", tmpDir+"/test-live-reload-remove.json"); err != nil {
|
|
t.Error("Error copying rules into temp dir")
|
|
}
|
|
if err = Copy("testdata/live_reload/test-live-reload-delete.json", tmpDir+"/test-live-reload-delete.json"); err != nil {
|
|
t.Error("Error copying rules into temp dir")
|
|
}
|
|
//wait for watcher to pick up the changes
|
|
time.Sleep(time.Second)
|
|
testNumRules(t, l, 4)
|
|
if err = os.Remove(tmpDir + "/test-live-reload-remove.json"); err != nil {
|
|
t.Error("Error Remove()ing file from temp dir")
|
|
}
|
|
if err = l.Delete("test-live-reload-delete"); err != nil {
|
|
t.Error("Error Delete()ing file from temp dir")
|
|
}
|
|
//wait for watcher to pick up the changes
|
|
time.Sleep(time.Second)
|
|
testNumRules(t, l, 2)
|
|
}
|
|
|
|
func randString() string {
|
|
rand.Seed(time.Now().UnixNano())
|
|
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
b := make([]rune, 10)
|
|
for i := range b {
|
|
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func Copy(src, dst string) error {
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
|
|
_, err = io.Copy(out, in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return out.Close()
|
|
}
|
|
|
|
func testNumRules(t *testing.T, l *Loader, num int) {
|
|
if l.NumRules() != num {
|
|
t.Error("rules number should be (2): ", num)
|
|
}
|
|
}
|
|
|
|
func testRulesOrder(t *testing.T, l *Loader) {
|
|
if l.rulesKeys[0] != "000-aaa-name" {
|
|
t.Error("Rules not in order (0): ", l.rulesKeys)
|
|
}
|
|
if l.rulesKeys[1] != "000-allow-chrome" {
|
|
t.Error("Rules not in order (1): ", l.rulesKeys)
|
|
}
|
|
if l.rulesKeys[2] != "001-deny-chrome" {
|
|
t.Error("Rules not in order (2): ", l.rulesKeys)
|
|
}
|
|
}
|
|
|
|
func testSortRules(t *testing.T, l *Loader) {
|
|
l.rulesKeys[1] = "001-deny-chrome"
|
|
l.rulesKeys[2] = "000-allow-chrome"
|
|
l.sortRules()
|
|
if l.rulesKeys[1] != "000-allow-chrome" {
|
|
t.Error("Rules not in order (1): ", l.rulesKeys)
|
|
}
|
|
if l.rulesKeys[2] != "001-deny-chrome" {
|
|
t.Error("Rules not in order (2): ", l.rulesKeys)
|
|
}
|
|
}
|
|
|
|
func testFindMatch(t *testing.T, l *Loader) {
|
|
conn.Process.Path = "/opt/google/chrome/chrome"
|
|
|
|
testFindPriorityMatch(t, l)
|
|
testFindDenyMatch(t, l)
|
|
testFindAllowMatch(t, l)
|
|
|
|
restoreConnection()
|
|
}
|
|
|
|
func testFindPriorityMatch(t *testing.T, l *Loader) {
|
|
match := l.FindFirstMatch(conn)
|
|
if match == nil {
|
|
t.Error("FindPriorityMatch didn't match")
|
|
}
|
|
// test 000-allow-chrome, priority == true
|
|
if match.Name != "000-allow-chrome" {
|
|
t.Error("findPriorityMatch: priority rule failed: ", match)
|
|
}
|
|
|
|
}
|
|
|
|
func testFindDenyMatch(t *testing.T, l *Loader) {
|
|
l.rules["000-allow-chrome"].Precedence = false
|
|
// test 000-allow-chrome, priority == false
|
|
// 001-deny-chrome must match
|
|
match := l.FindFirstMatch(conn)
|
|
if match == nil {
|
|
t.Error("FindDenyMatch deny didn't match")
|
|
}
|
|
if match.Name != "001-deny-chrome" {
|
|
t.Error("findDenyMatch: deny rule failed: ", match)
|
|
}
|
|
}
|
|
|
|
func testFindAllowMatch(t *testing.T, l *Loader) {
|
|
l.rules["000-allow-chrome"].Precedence = false
|
|
l.rules["001-deny-chrome"].Action = Allow
|
|
// test 000-allow-chrome, priority == false
|
|
// 001-deny-chrome must match
|
|
match := l.FindFirstMatch(conn)
|
|
if match == nil {
|
|
t.Error("FindAllowMatch allow didn't match")
|
|
}
|
|
if match.Name != "001-deny-chrome" {
|
|
t.Error("findAllowMatch: allow rule failed: ", match)
|
|
}
|
|
}
|
|
|
|
func testFindEnabled(t *testing.T, l *Loader) {
|
|
l.rules["000-allow-chrome"].Precedence = false
|
|
l.rules["001-deny-chrome"].Action = Allow
|
|
l.rules["001-deny-chrome"].Enabled = false
|
|
// test 000-allow-chrome, priority == false
|
|
// 001-deny-chrome must match
|
|
match := l.FindFirstMatch(conn)
|
|
if match == nil {
|
|
t.Error("FindEnabledMatch, match nil")
|
|
}
|
|
if match.Name == "001-deny-chrome" {
|
|
t.Error("findEnabledMatch: deny rule shouldn't have matched: ", match)
|
|
}
|
|
}
|
|
|
|
// test that changing the Duration of a temporary rule doesn't delete
|
|
// the new one, ignoring the old timer.
|
|
func testDurationChange(t *testing.T, l *Loader) {
|
|
l.rules["000-aaa-name"].Duration = "2s"
|
|
if err := l.replaceUserRule(l.rules["000-aaa-name"]); err != nil {
|
|
t.Error("testDurationChange, error replacing rule: ", err)
|
|
}
|
|
l.rules["000-aaa-name"].Duration = "1h"
|
|
if err := l.replaceUserRule(l.rules["000-aaa-name"]); err != nil {
|
|
t.Error("testDurationChange, error replacing rule: ", err)
|
|
}
|
|
time.Sleep(time.Second * 4)
|
|
if _, found := l.rules["000-aaa-name"]; !found {
|
|
t.Error("testDurationChange, error: rule has been deleted")
|
|
}
|
|
}
|