mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 08:34:40 +01:00
rules: improved operator list parsing and conversion
Previously when creating a new rule we followed these steps:
- Create a new protobuf Rule object from the ruleseditor or the
pop-ups.
- If the rule contained more than one operator, we converted the
list of operators to a JSON string.
- This JSON string was sent back to the daemon, and saved to the
DB.
- The list of operators were never expanded on the GUI, i.e., they
were not saved as a list of protobuf Operator objects.
- Once received in the daemon, the JSON string was parsed and
converted to a protobuf Operator list of objects.
Both, the JSON string and the list of protobuf Operator objects were
saved to disk, but the JSON string was ignored when loading the
rules.
Saving the list of operators as a JSON string was a problem if you
wanted to create or modify rules without the GUI.
Now when creating or modifying rules from the GUI, the list of operators
is no longer converted to JSON string. Instead the list is sent to the
daemon as a list of protobuf Operators, and saved as JSON objects.
Notes:
- The JSON string is no longer saved to disk as part of the rules.
- The list of operators is still saved as JSON string to the DB.
- About not enabled rules:
Previously, not enabled rules only had the list of operators as JSON
string, with the field list:[] empty.
Now the list of operators is saved as JSON objects, but if the rule
is not enabled, it won't be parsed/loaded.
Closes #1047
(cherry picked from commit b93051026e
)
This commit is contained in:
parent
4e7f1aeae7
commit
fe66f9aa17
12 changed files with 382 additions and 122 deletions
|
@ -98,6 +98,70 @@ func (l *Loader) Load(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Add adds a rule to the list of rules, and optionally saves it to disk.
|
||||
func (l *Loader) Add(rule *Rule, saveToDisk bool) error {
|
||||
l.addUserRule(rule)
|
||||
if saveToDisk {
|
||||
fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
|
||||
return l.Save(rule, fileName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace adds a rule to the list of rules, and optionally saves it to disk.
|
||||
func (l *Loader) Replace(rule *Rule, saveToDisk bool) error {
|
||||
if err := l.replaceUserRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
if saveToDisk {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
|
||||
return l.Save(rule, fileName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save a rule to disk.
|
||||
func (l *Loader) Save(rule *Rule, path string) error {
|
||||
rule.Updated = time.Now().Format(time.RFC3339)
|
||||
raw, err := json.MarshalIndent(rule, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err)
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(path, raw, 0600); err != nil {
|
||||
return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a rule from the list by name.
|
||||
// If the duration is Always (i.e: saved on disk), it'll attempt to delete
|
||||
// it from disk.
|
||||
func (l *Loader) Delete(ruleName string) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
rule := l.rules[ruleName]
|
||||
if rule == nil {
|
||||
return nil
|
||||
}
|
||||
l.cleanListsRule(rule)
|
||||
|
||||
delete(l.rules, ruleName)
|
||||
l.sortRules()
|
||||
|
||||
if rule.Duration != Always {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Delete() rule: %s", rule)
|
||||
return l.deleteRuleFromDisk(ruleName)
|
||||
}
|
||||
|
||||
func (l *Loader) loadRule(fileName string) error {
|
||||
raw, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
|
@ -117,7 +181,13 @@ func (l *Loader) loadRule(fileName string) error {
|
|||
l.cleanListsRule(oldRule)
|
||||
}
|
||||
|
||||
if r.Enabled {
|
||||
if !r.Enabled {
|
||||
// XXX: we only parse and load the Data field if the rule is disabled and the Data field is not empty
|
||||
// the rule will remain disabled.
|
||||
if err = l.unmarshalOperatorList(&r.Operator); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := r.Operator.Compile(); err != nil {
|
||||
log.Warning("Operator.Compile() error: %s: %s", err, r.Operator.Data)
|
||||
return fmt.Errorf("(1) Error compiling rule: %s", err)
|
||||
|
@ -191,41 +261,6 @@ func (l *Loader) cleanListsRule(oldRule *Rule) {
|
|||
}
|
||||
}
|
||||
|
||||
func (l *Loader) liveReloadWorker() {
|
||||
l.liveReloadRunning = true
|
||||
|
||||
log.Debug("Rules watcher started on path %s ...", l.path)
|
||||
if err := l.watcher.Add(l.path); err != nil {
|
||||
log.Error("Could not watch path: %s", err)
|
||||
l.liveReloadRunning = false
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-l.watcher.Events:
|
||||
// a new rule json file has been created or updated
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
if strings.HasSuffix(event.Name, ".json") {
|
||||
log.Important("Ruleset changed due to %s, reloading ...", path.Base(event.Name))
|
||||
if err := l.loadRule(event.Name); err != nil {
|
||||
log.Warning("%s", err)
|
||||
}
|
||||
}
|
||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
if strings.HasSuffix(event.Name, ".json") {
|
||||
log.Important("Rule deleted %s", path.Base(event.Name))
|
||||
// we only need to delete from memory rules of type Always,
|
||||
// because the Remove event is of a file, i.e.: Duration == Always
|
||||
l.deleteRule(event.Name)
|
||||
}
|
||||
}
|
||||
case err := <-l.watcher.Errors:
|
||||
log.Error("File system watcher error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loader) isTemporary(r *Rule) bool {
|
||||
return r.Duration != Restart && r.Duration != Always && r.Duration != Once
|
||||
}
|
||||
|
@ -247,6 +282,18 @@ func (l *Loader) setUniqueName(rule *Rule) {
|
|||
}
|
||||
}
|
||||
|
||||
// Deprecated: rule.Operator.Data no longer holds the operator list in json format as string.
|
||||
func (l *Loader) unmarshalOperatorList(op *Operator) error {
|
||||
if op.Type == List && len(op.List) == 0 && op.Data != "" {
|
||||
if err := json.Unmarshal([]byte(op.Data), &op.List); err != nil {
|
||||
return fmt.Errorf("error loading rule of type list: %s", err)
|
||||
}
|
||||
op.Data = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) sortRules() {
|
||||
l.rulesKeys = make([]string, 0, len(l.rules))
|
||||
for k := range l.rules {
|
||||
|
@ -278,22 +325,21 @@ func (l *Loader) replaceUserRule(rule *Rule) (err error) {
|
|||
l.cleanListsRule(oldRule)
|
||||
}
|
||||
|
||||
if err := l.unmarshalOperatorList(&rule.Operator); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
|
||||
if rule.Enabled {
|
||||
if err := rule.Operator.Compile(); err != nil {
|
||||
log.Warning("Operator.Compile() error: %s: %s", err, rule.Operator.Data)
|
||||
return fmt.Errorf("(2) Error compiling rule: %s", err)
|
||||
return fmt.Errorf("(2) error compiling rule: %s", err)
|
||||
}
|
||||
|
||||
if rule.Operator.Type == List {
|
||||
// TODO: use List protobuf object instead of un/marshalling to/from json
|
||||
if err = json.Unmarshal([]byte(rule.Operator.Data), &rule.Operator.List); err != nil {
|
||||
return fmt.Errorf("Error loading rule of type list: %s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(rule.Operator.List); i++ {
|
||||
if err := rule.Operator.List[i].Compile(); err != nil {
|
||||
log.Warning("Operator.Compile() error: %s: ", err)
|
||||
return fmt.Errorf("(2) Error compiling list rule: %s", err)
|
||||
return fmt.Errorf("(2) error compiling list rule: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -333,70 +379,39 @@ func (l *Loader) scheduleTemporaryRule(rule Rule) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Add adds a rule to the list of rules, and optionally saves it to disk.
|
||||
func (l *Loader) Add(rule *Rule, saveToDisk bool) error {
|
||||
l.addUserRule(rule)
|
||||
if saveToDisk {
|
||||
fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
|
||||
return l.Save(rule, fileName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (l *Loader) liveReloadWorker() {
|
||||
l.liveReloadRunning = true
|
||||
|
||||
// Replace adds a rule to the list of rules, and optionally saves it to disk.
|
||||
func (l *Loader) Replace(rule *Rule, saveToDisk bool) error {
|
||||
if err := l.replaceUserRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
if saveToDisk {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
|
||||
return l.Save(rule, fileName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save a rule to disk.
|
||||
func (l *Loader) Save(rule *Rule, path string) error {
|
||||
// When saving the rule, use always RFC3339 format for the Created field (#1140).
|
||||
rule.Updated = time.Now().Format(time.RFC3339)
|
||||
|
||||
raw, err := json.MarshalIndent(rule, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err)
|
||||
log.Debug("Rules watcher started on path %s ...", l.path)
|
||||
if err := l.watcher.Add(l.path); err != nil {
|
||||
log.Error("Could not watch path: %s", err)
|
||||
l.liveReloadRunning = false
|
||||
return
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(path, raw, 0600); err != nil {
|
||||
return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err)
|
||||
for {
|
||||
select {
|
||||
case event := <-l.watcher.Events:
|
||||
// a new rule json file has been created or updated
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
if strings.HasSuffix(event.Name, ".json") {
|
||||
log.Important("Ruleset changed due to %s, reloading ...", path.Base(event.Name))
|
||||
if err := l.loadRule(event.Name); err != nil {
|
||||
log.Warning("%s", err)
|
||||
}
|
||||
}
|
||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
if strings.HasSuffix(event.Name, ".json") {
|
||||
log.Important("Rule deleted %s", path.Base(event.Name))
|
||||
// we only need to delete from memory rules of type Always,
|
||||
// because the Remove event is of a file, i.e.: Duration == Always
|
||||
l.deleteRule(event.Name)
|
||||
}
|
||||
}
|
||||
case err := <-l.watcher.Errors:
|
||||
log.Error("File system watcher error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a rule from the list by name.
|
||||
// If the duration is Always (i.e: saved on disk), it'll attempt to delete
|
||||
// it from disk.
|
||||
func (l *Loader) Delete(ruleName string) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
rule := l.rules[ruleName]
|
||||
if rule == nil {
|
||||
return nil
|
||||
}
|
||||
l.cleanListsRule(rule)
|
||||
|
||||
delete(l.rules, ruleName)
|
||||
l.sortRules()
|
||||
|
||||
if rule.Duration != Always {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Delete() rule: %s", rule)
|
||||
return l.deleteRuleFromDisk(ruleName)
|
||||
}
|
||||
|
||||
// FindFirstMatch will try match the connection against the existing rule set.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package rule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
|
@ -95,6 +96,57 @@ func TestRuleLoaderInvalidRegexp(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
|
|
@ -63,7 +63,11 @@ func Create(name, description string, enabled, precedence, nolog bool, action Ac
|
|||
}
|
||||
|
||||
func (r *Rule) String() string {
|
||||
return fmt.Sprintf("%s: if(%s){ %s %s }", r.Name, r.Operator.String(), r.Action, r.Duration)
|
||||
enabled := "Disabled"
|
||||
if r.Enabled {
|
||||
enabled = "Enabled"
|
||||
}
|
||||
return fmt.Sprintf("[%s] %s: if(%s){ %s %s }", enabled, r.Name, r.Operator.String(), r.Action, r.Duration)
|
||||
}
|
||||
|
||||
// Match performs on a connection the checks a Rule has, to determine if it
|
||||
|
@ -90,7 +94,7 @@ func Deserialize(reply *protocol.Rule) (*Rule, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return Create(
|
||||
newRule := Create(
|
||||
reply.Name,
|
||||
reply.Description,
|
||||
reply.Enabled,
|
||||
|
@ -99,7 +103,24 @@ func Deserialize(reply *protocol.Rule) (*Rule, error) {
|
|||
Action(reply.Action),
|
||||
Duration(reply.Duration),
|
||||
operator,
|
||||
), nil
|
||||
)
|
||||
|
||||
if Type(reply.Operator.Type) == List {
|
||||
reply.Operator.Data = ""
|
||||
for i := 0; i < len(reply.Operator.List); i++ {
|
||||
newRule.Operator.List = append(
|
||||
newRule.Operator.List,
|
||||
Operator{
|
||||
Type: Type(reply.Operator.List[i].Type),
|
||||
Sensitive: Sensitive(reply.Operator.List[i].Sensitive),
|
||||
Operand: Operand(reply.Operator.List[i].Operand),
|
||||
Data: string(reply.Operator.List[i].Data),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return newRule, nil
|
||||
}
|
||||
|
||||
// Serialize translates a Rule to the protocol object
|
||||
|
@ -115,7 +136,7 @@ func (r *Rule) Serialize() *protocol.Rule {
|
|||
created = time.Now()
|
||||
}
|
||||
|
||||
return &protocol.Rule{
|
||||
protoRule := &protocol.Rule{
|
||||
Created: created.Unix(),
|
||||
Name: string(r.Name),
|
||||
Description: string(r.Description),
|
||||
|
@ -131,4 +152,18 @@ func (r *Rule) Serialize() *protocol.Rule {
|
|||
Data: string(r.Operator.Data),
|
||||
},
|
||||
}
|
||||
if r.Operator.Type == List {
|
||||
r.Operator.Data = ""
|
||||
for i := 0; i < len(r.Operator.List); i++ {
|
||||
protoRule.Operator.List = append(protoRule.Operator.List,
|
||||
&protocol.Operator{
|
||||
Type: string(r.Operator.List[i].Type),
|
||||
Sensitive: bool(r.Operator.List[i].Sensitive),
|
||||
Operand: string(r.Operator.List[i].Operand),
|
||||
Data: string(r.Operator.List[i].Data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return protoRule
|
||||
}
|
||||
|
|
31
daemon/rule/testdata/rule-disabled-operator-list-expanded.json
vendored
Normal file
31
daemon/rule/testdata/rule-disabled-operator-list-expanded.json
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"created": "2023-10-03T18:06:52.209804547+01:00",
|
||||
"updated": "2023-10-03T18:06:52.209857713+01:00",
|
||||
"name": "rule-disabled-with-operators-list-expanded",
|
||||
"enabled": false,
|
||||
"precedence": true,
|
||||
"action": "allow",
|
||||
"duration": "always",
|
||||
"operator": {
|
||||
"type": "list",
|
||||
"operand": "list",
|
||||
"sensitive": false,
|
||||
"data": "",
|
||||
"list": [
|
||||
{
|
||||
"type": "simple",
|
||||
"operand": "process.path",
|
||||
"sensitive": false,
|
||||
"data": "/usr/bin/telnet",
|
||||
"list": null
|
||||
},
|
||||
{
|
||||
"type": "simple",
|
||||
"operand": "dest.port",
|
||||
"sensitive": false,
|
||||
"data": "53",
|
||||
"list": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
17
daemon/rule/testdata/rule-disabled-operator-list.json
vendored
Normal file
17
daemon/rule/testdata/rule-disabled-operator-list.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"created": "2023-10-03T18:06:52.209804547+01:00",
|
||||
"updated": "2023-10-03T18:06:52.209857713+01:00",
|
||||
"name": "rule-disabled-with-operators-list-as-json-string",
|
||||
"enabled": false,
|
||||
"precedence": true,
|
||||
"action": "allow",
|
||||
"duration": "always",
|
||||
"operator": {
|
||||
"type": "list",
|
||||
"operand": "list",
|
||||
"sensitive": false,
|
||||
"data": "[{\"type\": \"simple\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"/usr/bin/telnet\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]",
|
||||
"list": [
|
||||
]
|
||||
}
|
||||
}
|
32
daemon/rule/testdata/rule-operator-list-data-empty.json
vendored
Normal file
32
daemon/rule/testdata/rule-operator-list-data-empty.json
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"created": "2023-10-03T18:06:52.209804547+01:00",
|
||||
"updated": "2023-10-03T18:06:52.209857713+01:00",
|
||||
"name": "rule-with-operator-list-data-empty",
|
||||
"enabled": true,
|
||||
"precedence": true,
|
||||
"action": "allow",
|
||||
"duration": "always",
|
||||
"operator": {
|
||||
"type": "list",
|
||||
"operand": "list",
|
||||
"sensitive": false,
|
||||
"data": "",
|
||||
"list": [
|
||||
{
|
||||
"type": "simple",
|
||||
"operand": "process.path",
|
||||
"sensitive": false,
|
||||
"data": "/usr/bin/telnet",
|
||||
"list": null
|
||||
},
|
||||
{
|
||||
"type": "simple",
|
||||
"operand": "dest.port",
|
||||
"sensitive": false,
|
||||
"data": "53",
|
||||
"list": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
31
daemon/rule/testdata/rule-operator-list.json
vendored
Normal file
31
daemon/rule/testdata/rule-operator-list.json
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"created": "2023-10-03T18:06:52.209804547+01:00",
|
||||
"updated": "2023-10-03T18:06:52.209857713+01:00",
|
||||
"name": "rule-with-operator-list",
|
||||
"enabled": true,
|
||||
"precedence": true,
|
||||
"action": "allow",
|
||||
"duration": "always",
|
||||
"operator": {
|
||||
"type": "list",
|
||||
"operand": "list",
|
||||
"sensitive": false,
|
||||
"data": "[{\"type\": \"simple\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"/usr/bin/telnet\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]",
|
||||
"list": [
|
||||
{
|
||||
"type": "simple",
|
||||
"operand": "process.path",
|
||||
"sensitive": false,
|
||||
"data": "/usr/bin/telnet",
|
||||
"list": null
|
||||
},
|
||||
{
|
||||
"type": "simple",
|
||||
"operand": "dest.port",
|
||||
"sensitive": false,
|
||||
"data": "53",
|
||||
"list": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -140,6 +140,7 @@ message Operator {
|
|||
string operand = 2;
|
||||
string data = 3;
|
||||
bool sensitive = 4;
|
||||
repeated Operator list = 5;
|
||||
}
|
||||
|
||||
message Rule {
|
||||
|
|
|
@ -18,7 +18,7 @@ from opensnitch.desktop_parser import LinuxDesktopParser
|
|||
from opensnitch.config import Config
|
||||
from opensnitch.version import version
|
||||
from opensnitch.actions import Actions
|
||||
from opensnitch.rules import Rules
|
||||
from opensnitch.rules import Rules, Rule
|
||||
|
||||
from opensnitch import ui_pb2
|
||||
|
||||
|
@ -685,10 +685,25 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": Config.OPERAND_PROCESS_PATH, "data": str(self._con.process_path)})
|
||||
|
||||
if is_list_rule:
|
||||
data.append({"type": self._rule.operator.type, "operand": self._rule.operator.operand, "data": self._rule.operator.data})
|
||||
data.append({
|
||||
"type": self._rule.operator.type,
|
||||
"operand": self._rule.operator.operand,
|
||||
"data": self._rule.operator.data
|
||||
})
|
||||
# We need to send back the operator list to the AskRule() call
|
||||
# as json string, in order to add it to the DB.
|
||||
self._rule.operator.data = json.dumps(data)
|
||||
self._rule.operator.type = Config.RULE_TYPE_LIST
|
||||
self._rule.operator.operand = Config.RULE_TYPE_LIST
|
||||
for op in data:
|
||||
self._rule.operator.list.extend([
|
||||
ui_pb2.Operator(
|
||||
type=op['type'],
|
||||
operand=op['operand'],
|
||||
sensitive=False if op.get('sensitive') == None else op['sensitive'],
|
||||
data="" if op.get('data') == None else op['data']
|
||||
)
|
||||
])
|
||||
|
||||
exists = self._rules.exists(self._rule, self._peer)
|
||||
if not exists:
|
||||
|
|
|
@ -4,7 +4,6 @@ from PyQt5.QtCore import QCoreApplication as QC
|
|||
from slugify import slugify
|
||||
from datetime import datetime
|
||||
import re
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import pwd
|
||||
|
@ -434,13 +433,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
if self.rule.operator.type != Config.RULE_TYPE_LIST:
|
||||
self._load_rule_operator(self.rule.operator)
|
||||
else:
|
||||
rule_options = json.loads(self.rule.operator.data)
|
||||
for r in rule_options:
|
||||
_sensitive = False
|
||||
if 'sensitive' in r:
|
||||
_sensitive = r['sensitive']
|
||||
|
||||
op = ui_pb2.Operator(type=r['type'], operand=r['operand'], data=r['data'], sensitive=_sensitive)
|
||||
for op in self.rule.operator.list:
|
||||
self._load_rule_operator(op)
|
||||
|
||||
return True
|
||||
|
@ -922,7 +915,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
'data': self.dstListsLine.text(),
|
||||
'sensitive': self.sensitiveCheck.isChecked()
|
||||
})
|
||||
self.rule.operator.data = json.dumps(rule_data)
|
||||
self.rule.operator.data = ""
|
||||
|
||||
if self.dstListRegexpCheck.isChecked():
|
||||
if self.dstRegexpListsLine.text() == "":
|
||||
|
@ -939,7 +932,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
'data': self.dstRegexpListsLine.text(),
|
||||
'sensitive': self.sensitiveCheck.isChecked()
|
||||
})
|
||||
self.rule.operator.data = json.dumps(rule_data)
|
||||
self.rule.operator.data = ""
|
||||
|
||||
if self.dstListNetsCheck.isChecked():
|
||||
if self.dstListNetsLine.text() == "":
|
||||
|
@ -956,7 +949,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
'data': self.dstListNetsLine.text(),
|
||||
'sensitive': self.sensitiveCheck.isChecked()
|
||||
})
|
||||
self.rule.operator.data = json.dumps(rule_data)
|
||||
self.rule.operator.data = ""
|
||||
|
||||
|
||||
if self.dstListIPsCheck.isChecked():
|
||||
|
@ -974,12 +967,22 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
'data': self.dstListIPsLine.text(),
|
||||
'sensitive': self.sensitiveCheck.isChecked()
|
||||
})
|
||||
self.rule.operator.data = json.dumps(rule_data)
|
||||
self.rule.operator.data = ""
|
||||
|
||||
if len(rule_data) >= 2:
|
||||
self.rule.operator.type = Config.RULE_TYPE_LIST
|
||||
self.rule.operator.operand = Config.RULE_TYPE_LIST
|
||||
self.rule.operator.data = json.dumps(rule_data)
|
||||
self.rule.operator.data = ""
|
||||
for rd in rule_data:
|
||||
self.rule.operator.list.extend([
|
||||
ui_pb2.Operator(
|
||||
type=rd['type'],
|
||||
operand=rd['operand'],
|
||||
data=rd['data'],
|
||||
sensitive=rd['sensitive']
|
||||
)
|
||||
])
|
||||
print(self.rule.operator.list)
|
||||
|
||||
elif len(rule_data) == 1:
|
||||
self.rule.operator.operand = rule_data[0]['operand']
|
||||
|
|
|
@ -51,6 +51,25 @@ class Rule():
|
|||
).timestamp())
|
||||
rule.created = created
|
||||
|
||||
try:
|
||||
# Operator list is always saved as json string to the db,
|
||||
# so we need to load the json string.
|
||||
if rule.operator.type == Config.RULE_TYPE_LIST:
|
||||
operators = json.loads(rule.operator.data)
|
||||
for op in operators:
|
||||
rule.operator.list.extend([
|
||||
ui_pb2.Operator(
|
||||
type=op['type'],
|
||||
operand=op['operand'],
|
||||
sensitive=False if op.get('sensitive') == None else op['sensitive'],
|
||||
data="" if op.get('data') == None else op['data']
|
||||
)
|
||||
])
|
||||
rule.operator.data = ""
|
||||
except Exception as e:
|
||||
print("new_from_records exception parsing operartor list:", e)
|
||||
|
||||
|
||||
return rule
|
||||
|
||||
class Rules(QObject):
|
||||
|
@ -83,6 +102,11 @@ class Rules(QObject):
|
|||
def add_rules(self, addr, rules):
|
||||
try:
|
||||
for _,r in enumerate(rules):
|
||||
# Operator list is always saved as json string to the db.
|
||||
rjson = json.loads(MessageToJson(r))
|
||||
if r.operator.type == Config.RULE_TYPE_LIST and rjson.get('operator') != None and rjson.get('operator').get('list') != None:
|
||||
r.operator.data = json.dumps(rjson.get('operator').get('list'))
|
||||
|
||||
self.add(datetime.now().strftime(DBDateFieldFormat),
|
||||
addr,
|
||||
r.name, r.description, str(r.enabled),
|
||||
|
|
|
@ -696,6 +696,10 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
rule.operator.data,
|
||||
str(datetime.fromtimestamp(rule.created).strftime("%Y-%m-%d %H:%M:%S"))
|
||||
)
|
||||
if rule.operator.type == Config.RULE_TYPE_LIST:
|
||||
# reset list operator data before sending it back to the
|
||||
# daemon.
|
||||
rule.operator.data = ""
|
||||
|
||||
elif kwargs['action'] == self.DELETE_RULE:
|
||||
self._db.delete_rule(kwargs['name'], kwargs['addr'])
|
||||
|
|
Loading…
Add table
Reference in a new issue