diff --git a/python/samba/gp_firewalld_ext.py b/python/samba/gp_firewalld_ext.py index e6dede47d69..0fbd87371e0 100644 --- a/python/samba/gp_firewalld_ext.py +++ b/python/samba/gp_firewalld_ext.py @@ -14,12 +14,145 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os +from subprocess import Popen, PIPE +from hashlib import blake2b +from shutil import which +import json from samba.gpclass import gp_pol_ext +def firewall_cmd(*args): + fw_cmd = which('firewall-cmd') + if fw_cmd is not None: + cmd = [fw_cmd] + cmd.extend(list(args)) + + p = Popen(cmd, stdout=PIPE, stderr=PIPE) + stdoutdata, _ = p.communicate() + return p.returncode, stdoutdata + else: + return -1, 'firewall-cmd not found' + +def rule_segment_parse(name, rule_segment): + if isinstance(rule_segment, str): + return ('%s=%s' % (name, rule_segment)) + ' ' + else: + return '%s %s ' % (name, + ' '.join(['%s=%s' % (k, v) for k, v in rule_segment.items()])) + class gp_firewalld_ext(gp_pol_ext): + def __str__(self): + return 'Security/Firewalld' + + def apply_zone(self, zone): + ret = firewall_cmd('--permanent', '--new-zone=%s' % zone)[0] + if ret != 0: + self.logger.error('Failed to add new zone %s' % zone) + else: + self.gp_db.store(str(self), 'zone:%s' % zone, zone) + # Default to matching the interface(s) for the default zone + ret, out = firewall_cmd('--list-interfaces') + if ret != 0: + self.logger.error('Failed to set interfaces for zone: %s' % zone) + for interface in out.strip().split(): + ret = firewall_cmd('--permanent', '--zone=%s' % zone, + '--add-interface=%s' % interface.decode()) + if ret != 0: + self.logger.error('Failed to set interfaces for zone: %s' % \ + zone) + + def apply_rules(self, rule_dict): + for zone, rules in rule_dict.items(): + for rule in rules: + if 'rule' in rule: + rule_parsed = rule_segment_parse('rule', rule['rule']) + else: + rule_parsed = 'rule ' + for segment in ['source', 'destination', 'service', 'port', + 'protocol', 'icmp-block', 'masquerade', + 'icmp-type', 'forward-port', 'source-port', + 'log', 'audit']: + names = [s for s in rule.keys() if s.startswith(segment)] + for name in names: + rule_parsed += rule_segment_parse(name, rule[name]) + actions = set(['accept', 'reject', 'drop', 'mark']) + segments = set(rule.keys()) + action = actions.intersection(segments) + if len(action) == 1: + rule_parsed += rule_segment_parse(list(action)[0], + rule[list(action)[0]]) + else: + self.logger.error('Invalid firewall rule syntax') + ret = firewall_cmd('--permanent', '--zone=%s' % zone, + '--add-rich-rule', rule_parsed.strip())[0] + if ret != 0: + self.logger.error('Failed to add firewall rule: %s' % \ + rule_parsed) + else: + rhash = blake2b(rule_parsed.encode()).hexdigest() + self.gp_db.store(str(self), 'rule:%s:%s' % (zone, rhash), + rule_parsed) + def process_group_policy(self, deleted_gpo_list, changed_gpo_list): - pass + for guid, settings in deleted_gpo_list: + self.gp_db.set_guid(guid) + if str(self) in settings: + for attribute, value in settings[str(self)].items(): + if attribute.startswith('zone'): + ret = firewall_cmd('--permanent', + '--delete-zone=%s' % value)[0] + if ret != 0: + self.logger.error('Failed to remove zone: %s' % \ + value) + else: + self.gp_db.delete(str(self), attribute) + elif attribute.startswith('rule'): + _, zone, _ = attribute.split(':') + ret = firewall_cmd('--permanent', '--zone=%s' % zone, + '--remove-rich-rule', value)[0] + if ret != 0: + self.logger.error('Failed to remove firewall' + ' rule: %s' % value) + else: + self.gp_db.delete(str(self), attribute) + self.gp_db.commit() + + for gpo in changed_gpo_list: + if gpo.file_sys_path: + section = 'Software\\Policies\\Samba\\Unix Settings\\Firewalld' + self.gp_db.set_guid(gpo.name) + pol_file = 'MACHINE/Registry.pol' + path = os.path.join(gpo.file_sys_path, pol_file) + pol_conf = self.parse(path) + if not pol_conf: + continue + for e in pol_conf.entries: + if e.keyname.startswith(section): + if e.keyname.endswith('Rules'): + self.apply_rules(json.loads(e.data)) + elif e.keyname.endswith('Zones'): + if e.valuename == '**delvals.': + continue + self.apply_zone(e.data) + self.gp_db.commit() def rsop(self, gpo): output = {} + pol_file = 'MACHINE/Registry.pol' + section = 'Software\\Policies\\Samba\\Unix Settings\\Firewalld' + if gpo.file_sys_path: + path = os.path.join(gpo.file_sys_path, pol_file) + pol_conf = self.parse(path) + if not pol_conf: + return output + for e in pol_conf.entries: + if e.keyname.startswith(section): + if e.keyname.endswith('Zone'): + if 'Zones' not in output.keys(): + output['Zones'] = [] + output['Zones'].append(e.data) + elif e.keyname.endswith('Rules'): + if 'Rules' not in output.keys(): + output['Rules'] = [] + output['Rules'].append(json.loads(e.data)) return output diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo deleted file mode 100644 index 74e2de0dd39..00000000000 --- a/selftest/knownfail.d/gpo +++ /dev/null @@ -1 +0,0 @@ -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_firewalld_ext diff --git a/source4/scripting/bin/samba-gpupdate b/source4/scripting/bin/samba-gpupdate index 0c1c2015287..5153225c536 100755 --- a/source4/scripting/bin/samba-gpupdate +++ b/source4/scripting/bin/samba-gpupdate @@ -48,6 +48,7 @@ from samba.gp_gnome_settings_ext import gp_gnome_settings_ext from samba.gp_cert_auto_enroll_ext import gp_cert_auto_enroll_ext from samba.gp_firefox_ext import gp_firefox_ext from samba.gp_chromium_ext import gp_chromium_ext, gp_chrome_ext +from samba.gp_firewalld_ext import gp_firewalld_ext from samba.credentials import Credentials import logging @@ -126,6 +127,7 @@ if __name__ == "__main__": gp_extensions.append(gp_firefox_ext) gp_extensions.append(gp_chromium_ext) gp_extensions.append(gp_chrome_ext) + gp_extensions.append(gp_firewalld_ext) gp_extensions.extend(machine_exts) elif opts.target == 'User': gp_extensions.append(gp_user_scripts_ext)