Skip to content

Commit

Permalink
ui: allow to ignore temporary rules, fixes, impovements
Browse files Browse the repository at this point in the history
- Added option to ignore temporary rules. All or only of duration
  "once".
  You can still use them when answering a pop-up, but if you check the
  option, the rules won't be added to the rules list.

- Fixed wrong behaviour when adding rules to the db/gui.
  When changing a rule duration (always->30s, 30s->always), if there
  were connections matching that rule, the rule was re-added to the
  db/rules list with the old duration, ending up with 2 rules in the
  list.
  This was caused by how stats are sent to the GUI. When populating the
  db with the stats, we were also adding the rules.

  Now we don't add the rules when adding the stats. Rules are added to
  the db everytime a node connects to the GUI, when answering a pop-up
  or whenever the user performs an operation on them.

  Performance has increased a little bit due to this.

- Fixed applying configuration to all nodes at once.
- Added help menu to the preferences dialog.
- Removed lists grid.
- A little bit of code reorganization.
  • Loading branch information
gustavo-iniguez-goya committed Jul 24, 2021
1 parent fbcab5a commit a3a171d
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 232 deletions.
42 changes: 40 additions & 2 deletions ui/opensnitch/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@
class Config:
__instance = None

HELP_URL = "https://github.com/evilsocket/opensnitch/wiki/Configurations"
HELP_URL = "https://github.com/evilsocket/opensnitch/wiki/"
HELP_CONFIG_URL = "https://github.com/evilsocket/opensnitch/wiki/Configurations"

RulesTypes = ("list", "lists", "simple", "regexp", "network")
RULE_TYPE_LIST = "list"
RULE_TYPE_LISTS = "lists"
RULE_TYPE_SIMPLE = "simple"
RULE_TYPE_REGEXP = "regexp"
RULE_TYPE_NETWORK = "network"
RulesTypes = (RULE_TYPE_LIST, RULE_TYPE_LISTS, RULE_TYPE_SIMPLE, RULE_TYPE_REGEXP, RULE_TYPE_NETWORK)

RULES_DURATION_FILTER = ()

DEFAULT_DURATION_IDX = 6 # until restart
DEFAULT_TARGET_PROCESS = 0
ACTION_DENY_IDX = 0
ACTION_ALLOW_IDX = 1
# don't translate
ACTION_ALLOW = "allow"
ACTION_DENY = "deny"
Expand All @@ -21,6 +31,7 @@ class Config:
DURATION_30m = "30m"
DURATION_15m = "15m"
DURATION_5m = "5m"
DURATION_30s = "30s"

POPUP_CENTER = 0
POPUP_TOP_RIGHT = 1
Expand All @@ -33,11 +44,14 @@ class Config:
DEFAULT_ACTION_KEY = "global/default_action"
DEFAULT_DURATION_KEY = "global/default_duration"
DEFAULT_TARGET_KEY = "global/default_target"
DEFAULT_IGNORE_RULES = "global/default_ignore_rules"
DEFAULT_IGNORE_TEMPORARY_RULES = "global/default_ignore_temporary_rules"
DEFAULT_POPUP_POSITION = "global/default_popup_position"
DEFAULT_POPUP_ADVANCED = "global/default_popup_advanced"
DEFAULT_POPUP_ADVANCED_DSTIP = "global/default_popup_advanced_dstip"
DEFAULT_POPUP_ADVANCED_DSTPORT = "global/default_popup_advanced_dstport"
DEFAULT_POPUP_ADVANCED_UID = "global/default_popup_advanced_uid"
DEFAULT_SERVER_ADDR = "global/server_address"
DEFAULT_DB_TYPE_KEY = "database/type"
DEFAULT_DB_FILE_KEY = "database/file"
# don't translate
Expand Down Expand Up @@ -68,6 +82,11 @@ def __init__(self):
self.setSettings(self.DEFAULT_DB_TYPE_KEY, Database.DB_TYPE_MEMORY)
self.setSettings(self.DEFAULT_DB_FILE_KEY, Database.DB_IN_MEMORY)

self.setRulesDurationFilter(
self.getBool(self.DEFAULT_IGNORE_RULES),
self.getInt(self.DEFAULT_IGNORE_TEMPORARY_RULES)
)

def reload(self):
self.settings = QtCore.QSettings("opensnitch", "settings")

Expand All @@ -89,3 +108,22 @@ def getInt(self, path):
return self.settings.value(path, False, type=int)
except Exception:
return 0

def getDefaultAction(self):
_default_action = self.getInt(self.DEFAULT_ACTION_KEY)
if _default_action == self.ACTION_ALLOW_IDX:
return self.ACTION_ALLOW
else:
return self.ACTION_DENY

def setRulesDurationFilter(self, ignore_temporary_rules=False, temp_rules=1):
if ignore_temporary_rules:
if temp_rules == 1:
Config.RULES_DURATION_FILTER = (Config.DURATION_ONCE)
elif temp_rules == 0:
Config.RULES_DURATION_FILTER = (
Config.DURATION_ONCE, Config.DURATION_30s, Config.DURATION_5m,
Config.DURATION_15m, Config.DURATION_30m, Config.DURATION_1h,
Config.DURATION_UNTIL_RESTART)
else:
Config.RULES_DURATION_FILTER = ()
68 changes: 56 additions & 12 deletions ui/opensnitch/dialogs/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
LOG_TAG = "[Preferences] "
_notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)

TAB_POPUPS = 0
TAB_UI = 1
TAB_NODES = 2
TAB_DB = 3

def __init__(self, parent=None):
QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)

Expand All @@ -33,12 +38,16 @@ def __init__(self, parent=None):

self.dbFileButton.setVisible(False)
self.dbLabel.setVisible(False)
self.dbType = None

self.acceptButton.clicked.connect(self._cb_accept_button_clicked)
self.applyButton.clicked.connect(self._cb_apply_button_clicked)
self.cancelButton.clicked.connect(self._cb_cancel_button_clicked)
self.helpButton.clicked.connect(self._cb_help_button_clicked)
self.popupsCheck.clicked.connect(self._cb_popups_check_toggled)
self.dbFileButton.clicked.connect(self._cb_file_db_clicked)
self.checkUIRules.toggled.connect(self._cb_check_ui_rules_toggled)
self.helpButton.setToolTipDuration(10 * 1000)

if QtGui.QIcon.hasThemeIcon("emblem-default") == False:
self.applyButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton")))
Expand Down Expand Up @@ -73,7 +82,7 @@ def showEvent(self, event):
self.comboNodeMonitorMethod.currentIndexChanged.connect(self._cb_node_needs_update)
self.comboNodeLogLevel.currentIndexChanged.connect(self._cb_node_needs_update)
self.comboNodeLogFile.currentIndexChanged.connect(self._cb_node_needs_update)
self.comboNodeAddress.currentIndexChanged.connect(self._cb_node_needs_update)
self.comboNodeAddress.currentTextChanged.connect(self._cb_node_needs_update)
self.checkInterceptUnknown.clicked.connect(self._cb_node_needs_update)
self.checkApplyToNodes.clicked.connect(self._cb_node_needs_update)
self.comboDBType.currentIndexChanged.connect(self._cb_db_type_changed)
Expand All @@ -95,6 +104,16 @@ def _load_settings(self):
self.comboUIDuration.setCurrentIndex(self._default_duration)
self.comboUIDialogPos.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_POPUP_POSITION))

self.comboUIRules.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES))
self.checkUIRules.setChecked(self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES))
self.comboUIRules.setEnabled(self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES))
#self._set_rules_duration_filter()

self._cfg.setRulesDurationFilter(
self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES),
self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES)
)

self.comboUIAction.setCurrentIndex(self._default_action)
self.comboUITarget.setCurrentIndex(int(self._default_target))
self.spinUITimeout.setValue(int(self._default_timeout))
Expand All @@ -106,7 +125,8 @@ def _load_settings(self):
self.dstPortCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT))
self.uidCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_UID))

self.comboDBType.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY))
self.dbType = self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY)
self.comboDBType.setCurrentIndex(self.dbType)
if self.comboDBType.currentIndex() != Database.DB_TYPE_MEMORY:
self.dbFileButton.setVisible(True)
self.dbLabel.setVisible(True)
Expand Down Expand Up @@ -152,10 +172,10 @@ def _reset_node_settings(self):
self.labelNodeVersion.setText("")

def _save_settings(self):
if self.tabWidget.currentIndex() == 0:
self._save_ui_config()
self._save_ui_config()
self._save_db_config()

elif self.tabWidget.currentIndex() == 1:
if self.tabWidget.currentIndex() == self.TAB_NODES:
self._show_status_label()

addr = self.comboNodes.currentText()
Expand Down Expand Up @@ -183,14 +203,17 @@ def _save_settings(self):

self._node_needs_update = False

elif self.tabWidget.currentIndex() == 2:
self._save_db_config()

def _save_db_config(self):
dbtype = self.comboDBType.currentIndex()
self._cfg.setSettings(Config.DEFAULT_DB_TYPE_KEY, dbtype)

if self.comboDBType.currentIndex() == self.dbType:
return

if dbtype == self._db.get_db_file():
return

if self.comboDBType.currentIndex() != Database.DB_TYPE_MEMORY:
if self.dbLabel.text() != "":
self._cfg.setSettings(Config.DEFAULT_DB_FILE_KEY, self.dbLabel.text())
Expand All @@ -206,7 +229,17 @@ def _save_db_config(self):
QC.translate("preferences", "Restart the GUI in order effects to take effect"),
QtWidgets.QMessageBox.Warning)

self.dbType = self.comboDBType.currentIndex()

def _save_ui_config(self):
self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES, int(self.comboUIRules.currentIndex()))
self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_RULES, bool(self.checkUIRules.isChecked()))
#self._set_rules_duration_filter()
self._cfg.setRulesDurationFilter(
bool(self.checkUIRules.isChecked()),
int(self.comboUIRules.currentIndex())
)

self._cfg.setSettings(self._cfg.DEFAULT_ACTION_KEY, self.comboUIAction.currentIndex())
self._cfg.setSettings(self._cfg.DEFAULT_DURATION_KEY, int(self.comboUIDuration.currentIndex()))
self._cfg.setSettings(self._cfg.DEFAULT_TARGET_KEY, self.comboUITarget.currentIndex())
Expand All @@ -230,10 +263,13 @@ def _save_node_config(self, notifObject, addr):
if error != None:
return error

self._nodes.save_node_config(addr, notifObject.data)
nid = self._nodes.send_notification(addr, notifObject, self._notification_callback)
if addr.startswith("unix://"):
self._cfg.setSettings(self._cfg.DEFAULT_DEFAULT_SERVER_ADDR, self.comboNodeAddress.currentText())
else:
self._nodes.save_node_config(addr, notifObject.data)
nid = self._nodes.send_notification(addr, notifObject, self._notification_callback)

self._notifications_sent[nid] = notifObject
self._notifications_sent[nid] = notifObject
except Exception as e:
print(self.LOG_TAG + "exception saving node config on %s: " % addr, e)
self._set_status_error(QC.translate("Exception saving node config {0}: {1}").format((addr, str(e))))
Expand Down Expand Up @@ -268,8 +304,8 @@ def _load_node_config(self, addr):
if self.checkApplyToNodes.isChecked():
node_config['Server']['Address'] = self.comboNodeAddress.currentText()
node_config['Server']['LogFile'] = self.comboNodeLogFile.currentText()
#else:
# print(addr, " doesn't have Server item")
else:
print(addr, " doesn't have Server item")
return json.dumps(node_config), None
except Exception as e:
print(self.LOG_TAG + "exception loading node config on %s: " % addr, e)
Expand Down Expand Up @@ -332,6 +368,11 @@ def _cb_apply_button_clicked(self):
def _cb_cancel_button_clicked(self):
self.reject()

def _cb_help_button_clicked(self):
QtWidgets.QToolTip.showText(QtGui.QCursor.pos(),
QC.translate("preferences",
"Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href=\"{0}\">{0}</a>").format(Config.HELP_URL))

def _cb_popups_check_toggled(self, checked):
self.spinUITimeout.setEnabled(not checked)
if not checked:
Expand All @@ -342,3 +383,6 @@ def _cb_node_combo_changed(self, index):

def _cb_node_needs_update(self):
self._node_needs_update = True

def _cb_check_ui_rules_toggled(self, state):
self.comboUIRules.setEnabled(state)
29 changes: 16 additions & 13 deletions ui/opensnitch/dialogs/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,39 +441,42 @@ def _get_duration(self, duration_idx):

def _get_combo_operator(self, combo, what_idx):
if combo.itemData(what_idx) == self.FIELD_PROC_PATH:
return "simple", "process.path", self._con.process_path
return Config.RULE_TYPE_SIMPLE, "process.path", self._con.process_path

elif combo.itemData(what_idx) == self.FIELD_PROC_ARGS:
return "simple", "process.command", ' '.join(self._con.process_args)
# this should not happen
if len(self._con.process_args) == 0:
return Config.RULE_TYPE_SIMPLE, "process.command", self._con.process_path
return Config.RULE_TYPE_SIMPLE, "process.command", ' '.join(self._con.process_args)

elif combo.itemData(what_idx) == self.FIELD_USER_ID:
return "simple", "user.id", "%s" % self._con.user_id
return Config.RULE_TYPE_SIMPLE, "user.id", "%s" % self._con.user_id

elif combo.itemData(what_idx) == self.FIELD_DST_PORT:
return "simple", "dest.port", "%s" % self._con.dst_port
return Config.RULE_TYPE_SIMPLE, "dest.port", "%s" % self._con.dst_port

elif combo.itemData(what_idx) == self.FIELD_DST_IP:
return "simple", "dest.ip", self._con.dst_ip
return Config.RULE_TYPE_SIMPLE, "dest.ip", self._con.dst_ip

elif combo.itemData(what_idx) == self.FIELD_DST_HOST:
return "simple", "dest.host", combo.currentText()
return Config.RULE_TYPE_SIMPLE, "dest.host", combo.currentText()

elif combo.itemData(what_idx) == self.FIELD_DST_NETWORK:
# strip "to ": "to x.x.x/20" -> "x.x.x/20"
# we assume that to is one word in all languages
parts = combo.currentText().split(' ')
text = parts[len(parts)-1]
return "network", "dest.network", text
return Config.RULE_TYPE_NETWORK, "dest.network", text

elif combo.itemData(what_idx) == self.FIELD_REGEX_HOST:
parts = combo.currentText().split(' ')
text = parts[len(parts)-1]
return "regexp", "dest.host", "%s" % '\.'.join(text.split('.')).replace("*", ".*")
return Config.RULE_TYPE_REGEXP, "dest.host", "%s" % '\.'.join(text.split('.')).replace("*", ".*")

elif combo.itemData(what_idx) == self.FIELD_REGEX_IP:
parts = combo.currentText().split(' ')
text = parts[len(parts)-1]
return "regexp", "dest.ip", "%s" % '\.'.join(text.split('.')).replace("*", ".*")
return Config.RULE_TYPE_REGEXP, "dest.ip", "%s" % '\.'.join(text.split('.')).replace("*", ".*")

def _on_deny_clicked(self):
self._default_action = self.ACTION_IDX_DENY
Expand Down Expand Up @@ -522,18 +525,18 @@ def _send_rule(self):
rule_temp_name = slugify("%s %s" % (rule_temp_name, _data))

if self.checkDstPort.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_DST_PORT:
data.append({"type": "simple", "operand": "dest.port", "data": str(self._con.dst_port)})
data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": "dest.port", "data": str(self._con.dst_port)})
rule_temp_name = slugify("%s %s" % (rule_temp_name, str(self._con.dst_port)))

if self.checkUserID.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_USER_ID:
data.append({"type": "simple", "operand": "user.id", "data": str(self._con.user_id)})
data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": "user.id", "data": str(self._con.user_id)})
rule_temp_name = slugify("%s %s" % (rule_temp_name, str(self._con.user_id)))

if self._is_list_rule():
data.append({"type": self._rule.operator.type, "operand": self._rule.operator.operand, "data": self._rule.operator.data})
self._rule.operator.data = json.dumps(data)
self._rule.operator.type = "list"
self._rule.operator.operand = ""
self._rule.operator.type = Config.RULE_TYPE_LIST
self._rule.operator.operand = Config.RULE_TYPE_LIST

self._rule.name = rule_temp_name

Expand Down
Loading

0 comments on commit a3a171d

Please sign in to comment.