Skip to content

Commit

Permalink
added priority and case-sensitive rules, fixes
Browse files Browse the repository at this point in the history
nodes:
 - added WA for backward compatibility with older protobuf libs.

stats:
 - added sensitive and priority rules columns
 - fixed selecting a node from the General tab.
 - if DstHost is empty, display the IP instead.
 - Hosts tab nw only lists hosts.

rules editor:
 - added options to define if a rule has precedence and if it's
   case-sensitive (for all fields for now). Default is case-insensitive.

closes #36
  • Loading branch information
gustavo-iniguez-goya committed Oct 22, 2020
1 parent e919bd5 commit ab46cca
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 235 deletions.
2 changes: 2 additions & 0 deletions ui/opensnitch/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ def _create_tables(self):
"node text, " \
"name text, " \
"enabled text, " \
"precedence text, " \
"action text, " \
"duration text, " \
"operator_type text, " \
"operator_sensitive text, " \
"operator_operand text, " \
"operator_data text, " \
"UNIQUE(node, name)"
Expand Down
54 changes: 45 additions & 9 deletions ui/opensnitch/dialogs/ruleseditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(self, parent=None, _rule=None):
self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self._cb_close_clicked)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._cb_apply_clicked)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self._cb_help_clicked)
self.protoCheck.toggled.connect(self._cb_proto_check_toggled)
self.procCheck.toggled.connect(self._cb_proc_check_toggled)
self.cmdlineCheck.toggled.connect(self._cb_cmdline_check_toggled)
self.dstPortCheck.toggled.connect(self._cb_dstport_check_toggled)
Expand All @@ -45,6 +46,9 @@ def __init__(self, parent=None, _rule=None):
if _rule != None:
self._load_rule(rule=_rule)

def _bool(self, s):
return s == 'True'

def _cb_accept_clicked(self):
pass

Expand All @@ -57,6 +61,9 @@ def _cb_reset_clicked(self):
def _cb_help_clicked(self):
QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_URL))

def _cb_proto_check_toggled(self, state):
self.protoCombo.setEnabled(state)

def _cb_proc_check_toggled(self, state):
self.procLine.setEnabled(state)

Expand Down Expand Up @@ -128,6 +135,9 @@ def _reset_state(self):
self.actionDenyRadio.setChecked(True)
self.durationCombo.setCurrentIndex(0)

self.protoCheck.setChecked(False)
self.protoCombo.setCurrentText("")

self.procCheck.setChecked(False)
self.procLine.setText("")

Expand All @@ -151,6 +161,7 @@ def _load_rule(self, addr=None, rule=None):

self.ruleNameEdit.setText(rule.name)
self.enableCheck.setChecked(rule.enabled)
self.precedenceCheck.setChecked(rule.precedence)
if rule.action == "deny":
self.actionDenyRadio.setChecked(True)
if rule.action == "allow":
Expand All @@ -167,6 +178,12 @@ def _load_rule(self, addr=None, rule=None):
self._load_rule_operator(op)

def _load_rule_operator(self, operator):
self.sensitiveCheck.setChecked(operator.sensitive)
if operator.operand == "protocol":
self.protoCheck.setChecked(True)
self.protoCombo.setEnabled(True)
self.protoCombo.setCurrentText(operator.data.upper())

if operator.operand == "process.path":
self.procCheck.setChecked(True)
self.procLine.setEnabled(True)
Expand Down Expand Up @@ -216,11 +233,12 @@ def _load_nodes(self, addr=None):

def _insert_rule_to_db(self, node_addr):
self._db.insert("rules",
"(time, node, name, enabled, action, duration, operator_type, operator_operand, operator_data)",
"(time, node, name, enabled, precedence, action, duration, operator_type, operator_sensitive, operator_operand, operator_data)",
(datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
node_addr, self.rule.name, str(self.rule.enabled),
node_addr, self.rule.name,
str(self.rule.enabled), str(self.rule.precedence),
self.rule.action, self.rule.duration, self.rule.operator.type,
self.rule.operator.operand, self.rule.operator.data),
self.rule.operator.operand, self.rule.operator.operand, self.rule.operator.data),
action_on_conflict="REPLACE")

def _add_rule(self):
Expand Down Expand Up @@ -279,16 +297,30 @@ def _save_rule(self):
self.rule = ui_pb2.Rule()
self.rule.name = self.ruleNameEdit.text()
self.rule.enabled = self.enableCheck.isChecked()
self.rule.precedence = self.precedenceCheck.isChecked()
self.rule.action = "deny" if self.actionDenyRadio.isChecked() else "allow"
self.rule.duration = self.durationCombo.currentText()

rule_data = []
if self.protoCheck.isChecked():
if self.protoCombo.currentText() == "":
return False, "protocol can not be empty, or uncheck it"

self.rule.operator.operand = "protocol"
self.rule.operator.data = self.protoCombo.currentText()
rule_data.append({"type": "simple", "operand": "protocol", "data": self.protoCombo.currentText().lower()})
if self._is_regex(self.protoCombo.currentText()):
rule_data[len(rule_data)-1]['type'] = "regexp"
if self._is_valid_regex(self.protoCombo.currentText()) == False:
return False, "Protocol regexp error"

if self.procCheck.isChecked():
if self.procLine.text() == "":
return False, "process path can not be empty"

self.rule.operator.operand = "process.path"
self.rule.operator.data = self.procLine.text()
self.rule.operator.sensitive = self.sensitiveCheck.isChecked()
rule_data.append({"type": "simple", "operand": "process.path", "data": self.procLine.text()})
if self._is_regex(self.procLine.text()):
rule_data[len(rule_data)-1]['type'] = "regexp"
Expand All @@ -301,6 +333,7 @@ def _save_rule(self):

self.rule.operator.operand = "process.command"
self.rule.operator.data = self.cmdlineLine.text()
self.rule.operator.sensitive = self.sensitiveCheck.isChecked()
rule_data.append({'type': 'simple', 'operand': 'process.command', 'data': self.cmdlineLine.text()})
if self._is_regex(self.cmdlineLine.text()):
rule_data[len(rule_data)-1]['type'] = "regexp"
Expand All @@ -325,6 +358,7 @@ def _save_rule(self):

self.rule.operator.operand = "dest.host"
self.rule.operator.data = self.dstHostLine.text()
self.rule.operator.sensitive = self.sensitiveCheck.isChecked()
rule_data.append({'type': 'simple', 'operand': 'dest.host', 'data': self.dstHostLine.text()})
if self._is_regex(self.dstHostLine.text()):
rule_data[len(rule_data)-1]['type'] = "regexp"
Expand Down Expand Up @@ -375,12 +409,14 @@ def edit_rule(self, records, _addr=None):
self._reset_state()

self.rule = ui_pb2.Rule(name=records.value(2))
self.rule.enabled = bool(records.value(3))
self.rule.action = records.value(4)
self.rule.duration = records.value(5)
self.rule.operator.type = records.value(6)
self.rule.operator.operand = records.value(7)
self.rule.operator.data = "" if records.value(8) == None else str(records.value(8))
self.rule.enabled = self._bool(records.value(3))
self.rule.precedence = self._bool(records.value(4))
self.rule.action = records.value(5)
self.rule.duration = records.value(6)
self.rule.operator.type = records.value(7)
self.rule.operator.sensitive = self._bool(str(records.value(8)))
self.rule.operator.operand = records.value(9)
self.rule.operator.data = "" if records.value(10) == None else str(records.value(10))

self._old_rule_name = records.value(2)

Expand Down
37 changes: 27 additions & 10 deletions ui/opensnitch/dialogs/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ def _set_events_query(self):
if self.comboAction.currentText() != "-":
action = "Action = \"" + self.comboAction.currentText().lower() + "\""

# FIXME: use prepared statements
if filter_text == "":
if action != "":
qstr += " WHERE " + action
Expand Down Expand Up @@ -764,20 +765,24 @@ def _set_rules_query(self, data, node=""):
"r.node as Node, " \
"count(c.process) as Hits, " \
"r.enabled as Enabled, " \
"r.precedence as Precedence, " \
"r.action as Action, " \
"r.duration as Duration, " \
"r.operator_type as RuleType, " \
"r.operator_sensitive as CaseSensitive, " \
"r.operator_operand as RuleOperand, " \
"r.operator_data as RuleData, " \
"c.uid as UserID, " \
"c.protocol as Protocol, " \
"c.dst_port as DstPort, " \
"c.dst_host as DstIP, " \
"CASE c.dst_host WHEN ''" \
" THEN c.dst_ip " \
" ELSE c.dst_host " \
"END Destination, " \
"c.process as Process, " \
"c.process_args as Args, " \
"c.process_cwd as CWD " \
"FROM rules as r, connections as c " \
"WHERE %s r.name = '%s' AND r.name = c.rule AND r.node = c.node GROUP BY Process, Args, UserID, DstIP, DstPort %s" % (node, data, self._get_order()))
"WHERE %s r.name = '%s' AND r.name = c.rule AND r.node = c.node GROUP BY Process, Args, UserID, Destination, DstPort %s" % (node, data, self._get_order()))

def _set_hosts_query(self, data):
model = self._get_active_table().model()
Expand Down Expand Up @@ -805,7 +810,10 @@ def _set_process_query(self, data):
"count(c.dst_host) as Hits, " \
"c.action as Action, " \
"c.uid as UserID, " \
"c.dst_host || ' -> ' || c.dst_port as Destination, " \
"CASE c.dst_host WHEN ''" \
" THEN c.dst_ip || ' -> ' || c.dst_port " \
" ELSE c.dst_host || ' -> ' || c.dst_port " \
"END Destination, " \
"c.pid as PID, " \
"c.process_args as Args, " \
"c.process_cwd as CWD, " \
Expand All @@ -822,14 +830,17 @@ def _set_addrs_query(self, data):
"c.action as Action, " \
"c.uid as UserID, " \
"c.protocol as Protocol, " \
"c.dst_host as DstHost, " \
"CASE c.dst_host WHEN ''" \
" THEN c.dst_ip " \
" ELSE c.dst_host " \
"END Destination, " \
"c.dst_port as DstPort, " \
"c.process || ' (' || c.pid || ')' as Process, " \
"c.process_args as Args, " \
"c.process_cwd as CWD, " \
"c.rule as Rule " \
"FROM addrs as a, connections as c " \
"WHERE a.what = '%s' AND c.dst_ip = a.what GROUP BY c.pid, Process, Args, DstPort, DstHost, Protocol, Action, UserID, Node %s" % (data, self._get_order()))
"WHERE a.what = '%s' AND c.dst_ip = a.what GROUP BY c.pid, Process, Args, DstPort, Destination, Protocol, Action, UserID, Node %s" % (data, self._get_order()))

def _set_ports_query(self, data):
model = self._get_active_table().model()
Expand All @@ -841,13 +852,16 @@ def _set_ports_query(self, data):
"c.uid as UserID, " \
"c.protocol as Protocol, " \
"c.dst_ip as DstIP, " \
"c.dst_host as DstHost, " \
"CASE c.dst_host WHEN ''" \
" THEN c.dst_ip " \
" ELSE c.dst_host " \
"END Destination, " \
"c.process || ' (' || c.pid || ')' as Process, " \
"c.process_args as Args, " \
"c.process_cwd as CWD, " \
"c.rule as Rule " \
"FROM ports as p, connections as c " \
"WHERE p.what = '%s' AND c.dst_port = p.what GROUP BY c.pid, Process, Args, DstHost, DstIP, Protocol, Action, UserID, Node %s" % (data, self._get_order()))
"WHERE p.what = '%s' AND c.dst_port = p.what GROUP BY c.pid, Process, Args, Destination, DstIP, Protocol, Action, UserID, Node %s" % (data, self._get_order()))

def _set_users_query(self, data):
model = self._get_active_table().model()
Expand All @@ -858,14 +872,17 @@ def _set_users_query(self, data):
"c.action as Action, " \
"c.protocol as Protocol, " \
"c.dst_ip as DstIP, " \
"c.dst_host as DstHost, " \
"CASE c.dst_host WHEN ''" \
" THEN c.dst_ip " \
" ELSE c.dst_host " \
"END Destination, " \
"c.dst_port as DstPort, " \
"c.process || ' (' || c.pid || ')' as Process, " \
"c.process_args as Args, " \
"c.process_cwd as CWD, " \
"c.rule as Rule " \
"FROM users as u, connections as c " \
"WHERE u.what = '%s' AND u.what LIKE '%%(' || c.uid || ')' GROUP BY c.pid, Process, Args, DstIP, DstHost, DstPort, Protocol, Action, Node %s" % (data, self._get_order()))
"WHERE u.what = '%s' AND u.what LIKE '%%(' || c.uid || ')' GROUP BY c.pid, Process, Args, DstIP, Destination, DstPort, Protocol, Action, Node %s" % (data, self._get_order()))

def _on_save_clicked(self):
tab_idx = self.tabWidget.currentIndex()
Expand Down
14 changes: 9 additions & 5 deletions ui/opensnitch/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def count(self):

def add(self, context, client_config=None):
try:
proto, _addr = self._get_addr(context.peer())
proto, _addr = self.get_addr(context.peer())
addr = "%s:%s" % (proto, _addr)
if addr not in self._nodes:
self._nodes[addr] = {
Expand Down Expand Up @@ -59,11 +59,12 @@ def add_rules(self, addr, rules):
try:
for _,r in enumerate(rules):
self._db.insert("rules",
"(time, node, name, enabled, action, duration, operator_type, operator_operand, operator_data)",
"(time, node, name, enabled, precedence, action, duration, operator_type, operator_sensitive, operator_operand, operator_data)",
(datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
addr,
r.name, str(r.enabled), r.action, r.duration,
r.name, str(r.enabled), str(r.precedence), r.action, r.duration,
r.operator.type,
str(r.operator.sensitive),
r.operator.operand,
r.operator.data),
action_on_conflict="IGNORE")
Expand All @@ -75,7 +76,7 @@ def delete_all(self):
self._nodes = {}

def delete(self, peer):
proto, addr = self._get_addr(peer)
proto, addr = self.get_addr(peer)
addr = "%s:%s" % (proto, addr)
# Force the node to get one new item from queue,
# in order to loop and exit.
Expand Down Expand Up @@ -103,8 +104,11 @@ def get_client_config(self, client_config):

return client_config

def _get_addr(self, peer):
def get_addr(self, peer):
peer = peer.split(":")
# WA for backward compatibility
if peer[0] == "unix" and peer[1] == "":
peer[1] = "local"
return peer[0], peer[1]

def get_notifications(self):
Expand Down
Loading

0 comments on commit ab46cca

Please sign in to comment.