Skip to content

Commit 09bb7e5

Browse files
ui,fw: allow to configure nodes policies
Previously you could only change firewall policies of one node. Now if there's more than one node you can select the node, or change the policies of all nodes.
1 parent 12d523b commit 09bb7e5

File tree

2 files changed

+229
-166
lines changed

2 files changed

+229
-166
lines changed

ui/opensnitch/dialogs/firewall.py

Lines changed: 120 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class FirewallDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
2727
POLICY_ACCEPT = 0
2828
POLICY_DROP = 1
2929

30+
ALL_NODES = "all"
31+
3032
_notification_callback = QtCore.pyqtSignal(str, ui_pb2.NotificationReply)
3133

3234
def __init__(self, parent=None, appicon=None, node=None):
@@ -64,6 +66,7 @@ def __init__(self, parent=None, appicon=None, node=None):
6466
self.cmdAllowINService.clicked.connect(self._cb_allow_in_service_clicked)
6567
self.comboInput.currentIndexChanged.connect(lambda: self._cb_combo_policy_changed(self.COMBO_IN))
6668
self.comboProfile.currentIndexChanged.connect(self._cb_combo_profile_changed)
69+
self.comboNodes.currentIndexChanged.connect(self._cb_combo_nodes_changed)
6770
self.sliderFwEnable.valueChanged.connect(self._cb_enable_fw_changed)
6871
self.cmdClose.clicked.connect(self._cb_close_clicked)
6972
self.cmdHelp.clicked.connect(
@@ -103,7 +106,16 @@ def _cb_notification_callback(self, addr, reply):
103106

104107
@QtCore.pyqtSlot(int)
105108
def _cb_nodes_updated(self, total):
106-
self._check_fw_status()
109+
if self._nodes.count() <= 1:
110+
self.load_fw_policies()
111+
112+
def _cb_combo_nodes_changed(self, idx):
113+
nIdx = self.comboNodes.currentIndex()
114+
addr = self.comboNodes.itemData(nIdx)
115+
if nIdx >= 1:
116+
self.block_combo_signals()
117+
self.load_node_fw_policy(addr)
118+
self.block_combo_signals(False)
107119

108120
def _cb_combo_profile_changed(self, idx):
109121
combo_profile = self._fw_profiles[idx]
@@ -184,14 +196,13 @@ def _cb_enable_fw_changed(self, enable):
184196
self.sliderFwEnable.setValue(False)
185197
self.sliderFwEnable.blockSignals(False)
186198
return
187-
self.enable_fw(enable)
199+
nIdx = self.comboNodes.currentIndex()
200+
addr = self.comboNodes.itemData(nIdx)
201+
self.enable_fw(addr, enable)
188202

189203
def _cb_close_clicked(self):
190204
self._close()
191205

192-
def _load_nodes(self):
193-
self._nodes = self._nodes.get()
194-
195206
def _close(self):
196207
self.hide()
197208

@@ -202,63 +213,69 @@ def _change_fw_backend(self, addr, node_cfg):
202213
def showEvent(self, event):
203214
super(FirewallDialog, self).showEvent(event)
204215
self._reset_fields()
205-
self._check_fw_status()
216+
self._load_nodes()
217+
self.load_fw_policies()
206218
self._fw_profiles = FwProfiles.Profiles.load_predefined_profiles()
207219
self.comboProfile.blockSignals(True)
208220
for pr in self._fw_profiles:
209221
self.comboProfile.addItem([pr[k] for k in pr][0]['Name'])
210222
self.comboProfile.blockSignals(False)
211223

224+
def _load_nodes(self):
225+
self.comboNodes.blockSignals(True)
226+
self.comboNodes.clear()
227+
node_list = self._nodes.get()
228+
229+
self.comboNodes.addItem(QC.translate("firewall", "All"), self.ALL_NODES)
230+
for node in node_list:
231+
hostname = self._nodes.get_node_hostname(node)
232+
self.comboNodes.addItem(hostname + " - " + node, node)
233+
234+
show_nodes = len(node_list) > 1
235+
if show_nodes is False:
236+
self.comboNodes.setCurrentIndex(1)
237+
self.comboNodes.setVisible(show_nodes)
238+
239+
self.comboNodes.blockSignals(False)
240+
212241
def send_notification(self, node_addr, fw_config):
213242
self._set_status_message(QC.translate("firewall", "Applying changes..."))
214-
nid, notif = self._nodes.reload_fw(node_addr, fw_config, self._notification_callback)
215-
self._notifications_sent[nid] = {'addr': node_addr, 'notif': notif}
216243

217-
def _check_fw_status(self):
244+
nIdx = self.comboNodes.currentIndex()
245+
addr = self.comboNodes.itemData(nIdx)
246+
if addr == self.ALL_NODES and self._nodes.count() > 1:
247+
for addr in self._nodes.get():
248+
nid, notif = self._nodes.reload_fw(addr, fw_config, self._notification_callback)
249+
self._notifications_sent[nid] = {'addr': addr, 'notif': notif}
250+
251+
return
252+
253+
nid, notif = self._nodes.reload_fw(addr, fw_config, self._notification_callback)
254+
self._notifications_sent[nid] = {'addr': addr, 'notif': notif}
255+
256+
def load_fw_policies(self, node_addr=None):
218257
self.lblFwStatus.setText("")
219258
self.sliderFwEnable.blockSignals(True)
220-
self.comboInput.blockSignals(True)
221-
self.comboOutput.blockSignals(True)
222-
self.comboProfile.blockSignals(True)
259+
self.block_combo_signals()
223260

224261
self._disable_widgets()
225262

263+
enableFw = False
226264
try:
227-
enableFw = False
228265
enableFwBtn = (self._nodes.count() > 0)
229266
self.sliderFwEnable.setEnabled(enableFwBtn)
230267
if not enableFwBtn:
231268
return
232269

233-
# TODO: handle nodes' firewall properly
234-
for addr in self._nodes.get():
235-
node = self._nodes.get_node(addr)
236-
self._fwConfig = node['firewall']
237-
enableFw |= self._fwConfig.Enabled
270+
enableFw = self._nodes.count() > 1
271+
if node_addr is None:
238272

239-
if self.fw_is_incompatible(addr, node):
240-
enableFw = False
273+
if self._nodes.count() == 1:
274+
nIdx = self.comboNodes.currentIndex()
275+
node_addr = self.comboNodes.itemData(nIdx)
276+
enableFw = self.load_node_fw_policy(node_addr)
241277
return
242278

243-
# XXX: Here we loop twice over the chains. We could have 1 loop.
244-
pol_in = self._fw.chains.get_policy(addr, Fw.Hooks.INPUT.value)
245-
pol_out = self._fw.chains.get_policy(addr, Fw.Hooks.OUTPUT.value, Fw.ChainType.MANGLE.value)
246-
247-
if pol_in != None:
248-
self.comboInput.setCurrentIndex(
249-
Fw.Policy.values().index(pol_in)
250-
)
251-
else:
252-
self._set_status_error(QC.translate("firewall", "Error getting INPUT chain policy"))
253-
self._disable_widgets()
254-
if pol_out != None:
255-
self.comboOutput.setCurrentIndex(
256-
Fw.Policy.values().index(pol_out)
257-
)
258-
else:
259-
self._set_status_error(QC.translate("firewall", "Error getting OUTPUT chain policy"))
260-
self._disable_widgets()
261-
262279
except Exception as e:
263280
self._set_status_error("Firewall status error (report on github please): {0}".format(e))
264281

@@ -272,9 +289,44 @@ def _check_fw_status(self):
272289
self.sliderFwEnable.setValue(enableFw)
273290

274291
self.sliderFwEnable.blockSignals(False)
275-
self.comboInput.blockSignals(False)
276-
self.comboOutput.blockSignals(False)
277-
self.comboProfile.blockSignals(False)
292+
self.block_combo_signals(False)
293+
294+
def load_node_fw_policy(self, addr):
295+
enableFw = False
296+
try:
297+
node = self._nodes.get_node(addr)
298+
self._fwConfig = node['firewall']
299+
enableFw |= self._fwConfig.Enabled
300+
301+
if self.fw_is_incompatible(addr, node):
302+
enableFw = False
303+
return
304+
305+
# XXX: Here we loop twice over the chains. We could have 1 loop.
306+
pol_in = self._fw.chains.get_policy(addr, Fw.Hooks.INPUT.value)
307+
pol_out = self._fw.chains.get_policy(addr, Fw.Hooks.OUTPUT.value, Fw.ChainType.MANGLE.value)
308+
309+
if pol_in != None:
310+
self.comboInput.setCurrentIndex(
311+
Fw.Policy.values().index(pol_in)
312+
)
313+
else:
314+
self._set_status_error(QC.translate("firewall", "Error getting INPUT chain policy"))
315+
self._disable_widgets()
316+
if pol_out != None:
317+
self.comboOutput.setCurrentIndex(
318+
Fw.Policy.values().index(pol_out)
319+
)
320+
else:
321+
self._set_status_error(QC.translate("firewall", "Error getting OUTPUT chain policy"))
322+
self._disable_widgets()
323+
324+
except Exception as e:
325+
self._set_status_error("Firewall status error (report on github please): {0}".format(e))
326+
enableFw = False
327+
328+
return enableFw
329+
278330

279331
def fw_is_incompatible(self, addr, node):
280332
"""Check if the fw is compatible with this GUI.
@@ -290,7 +342,7 @@ def fw_is_incompatible(self, addr, node):
290342
if self.isHidden() == False and self.change_fw(addr, node_cfg):
291343
node_cfg['Firewall'] = "nftables"
292344
self.sliderFwEnable.setEnabled(True)
293-
self.enable_fw(True)
345+
self.enable_fw(addr, True)
294346
self._change_fw_backend(addr, node_cfg)
295347
return False
296348
incompatible = True
@@ -321,7 +373,7 @@ def change_fw(self, addr, node_cfg):
321373

322374
return False
323375

324-
def enable_fw(self, enable):
376+
def enable_fw(self, addr, enable):
325377
try:
326378
self._disable_widgets(not enable)
327379
if enable:
@@ -357,30 +409,31 @@ def enable_fw(self, enable):
357409
)
358410
return
359411

360-
for addr in self._nodes.get():
361-
# FIXME:
362-
# Due to how the daemon reacts to events when the fw configuration
363-
# is modified, changing the policy + disabling the fw doesn't work
364-
# as expected.
365-
# The daemon detects that the fw is disabled, and it never changes
366-
# the policy.
367-
# As a workaround to this problem, we send 2 fw changes:
368-
# - one for changing the policy
369-
# - another one for disabling the fw
412+
# FIXME:
413+
# Due to how the daemon reacts to events when the fw configuration
414+
# is modified, changing the policy + disabling the fw doesn't work
415+
# as expected.
416+
# The daemon detects that the fw is disabled, and it never changes
417+
# the policy.
418+
# As a workaround to this problem, we send 2 fw changes:
419+
# - one for changing the policy
420+
# - another one for disabling the fw
370421

371-
fwcfg = self._nodes.get_node(addr)['firewall']
372-
self.send_notification(addr, fwcfg)
373-
time.sleep(0.5)
374-
fwcfg.Enabled = True if enable else False
375-
self.send_notification(addr, fwcfg)
422+
fwcfg = self._nodes.get_node(addr)['firewall']
423+
self.send_notification(addr, fwcfg)
424+
time.sleep(0.5)
425+
fwcfg.Enabled = True if enable else False
426+
self.send_notification(addr, fwcfg)
376427

377428
self.lblStatusIcon.setEnabled(enable)
378429
self.policiesBox.setEnabled(enable)
379430

380431
time.sleep(0.5)
381432

382433
except Exception as e:
383-
QC.translate("firewall", "Error: {0}".format(e))
434+
self._set_status_error(
435+
QC.translate("firewall", "Error: {0}".format(e))
436+
)
384437

385438
def load_rule(self, addr, uuid):
386439
self._fwrule_dialog.load(addr, uuid)
@@ -416,9 +469,16 @@ def _reset_status_message(self):
416469
def _reset_fields(self):
417470
self._reset_status_message()
418471

472+
def block_combo_signals(self, state=True):
473+
self.comboInput.blockSignals(state)
474+
self.comboOutput.blockSignals(state)
475+
self.comboNodes.blockSignals(state)
476+
self.comboProfile.blockSignals(state)
477+
419478
def _disable_widgets(self, disable=True):
420479
self.comboInput.setEnabled(not disable)
421480
self.comboOutput.setEnabled(not disable)
422481
self.cmdNewRule.setEnabled(not disable)
423482
self.cmdAllowOUTService.setEnabled(not disable)
424483
self.cmdAllowINService.setEnabled(not disable)
484+
self.comboNodes.setEnabled(not disable)

0 commit comments

Comments
 (0)