class FirewallD: def __init__ (self): try: from firewall.client import FirewallClient self._fw = FirewallClient () if not self._fw.connected: debugprint ("FirewallD seems to be installed but not running") self._fw = None self._zone = None self.running = False return zone_name = self._get_active_zone () if zone_name: self._zone = self._fw.config().getZoneByName (zone_name) else: self._zone = None self.running = True debugprint ("Using /org/fedoraproject/FirewallD1") except (ImportError, dbus.exceptions.DBusException): self._fw = None self._zone = None self.running = False def _get_active_zone (self): zones = list(self._fw.getActiveZones().keys()) if not zones: debugprint ("FirewallD: no changeable zone") return None elif len (zones) == 1: # most probable case return zones[0] else: # Do we need to handle the 'more active zones' case ? # It's quite unlikely case because that would mean that more # network connections are up and running and they are # in different network zones at the same time. debugprint ("FirewallD returned more zones, taking first one") return zones[0] def _get_fw_data (self, reply_handler=None, error_handler=None): try: debugprint ("%s in _get_fw_data: _fw_data is %s" % (self, repr(self._fw_data.getServices()))) if self._fw_data: debugprint ("Using cached firewall data") if reply_handler: reply_handler (self._fw_data) except AttributeError: try: self._fw_data = self._zone.getSettings () debugprint ("Firewall data obtained") if reply_handler: reply_handler (self._fw_data) except (dbus.exceptions.DBusException, AttributeError, ValueError) as e: self._fw_data = None debugprint ("Exception examining firewall") if error_handler: error_handler (e) return self._fw_data def read (self, reply_handler=None, error_handler=None): if reply_handler: self._get_fw_data (reply_handler, error_handler) else: self._get_fw_data () def write (self): try: if self._zone: self._zone.update (self._fw_data) self._fw.reload () except dbus.exceptions.DBusException: nonfatalException () def add_service (self, service): if not self._get_fw_data (): return self._fw_data.addService (service) def check_ipp_client_allowed (self): if not self._get_fw_data (): return True return (IPP_CLIENT_SERVICE in self._fw_data.getServices () or [IPP_CLIENT_PORT, IPP_CLIENT_PROTOCOL] in self._fw_data.getPorts ()) def check_ipp_server_allowed (self): if not self._get_fw_data (): return True return (IPP_SERVER_SERVICE in self._fw_data.getServices () or [IPP_SERVER_PORT, IPP_SERVER_PROTOCOL] in self._fw_data.getPorts ()) def check_samba_client_allowed (self): if not self._get_fw_data (): return True return (SAMBA_CLIENT_SERVICE in self._fw_data.getServices ()) def check_mdns_allowed (self): if not self._get_fw_data (): return True return (MDNS_SERVICE in self._fw_data.getServices () or [MDNS_PORT, MDNS_PROTOCOL] in self._fw_data.getPorts ())
class FirewallD: def __init__(self): try: from firewall.client import FirewallClient self._fw = FirewallClient() if not self._fw.connected: debugprint("FirewallD seems to be installed but not running") self._fw = None self._zone = None self.running = False return zone_name = self._get_active_zone() if zone_name: self._zone = self._fw.config().getZoneByName(zone_name) else: self._zone = None self.running = True debugprint("Using /org/fedoraproject/FirewallD1") except (ImportError, dbus.exceptions.DBusException): self._fw = None self._zone = None self.running = False def _get_active_zone(self): zones = self._fw.getActiveZones().keys() if not zones: debugprint("FirewallD: no changeable zone") return None elif len(zones) == 1: # most probable case return zones[0] else: # Do we need to handle the 'more active zones' case ? # It's quite unlikely case because that would mean that more # network connections are up and running and they are # in different network zones at the same time. debugprint("FirewallD returned more zones, taking first one") return zones[0] def _get_fw_data(self, reply_handler=None, error_handler=None): try: debugprint("%s in _get_fw_data: _fw_data is %s" % (self, repr(self._fw_data.getServices()))) if self._fw_data: debugprint("Using cached firewall data") if reply_handler: reply_handler(self._fw_data) except AttributeError: try: self._fw_data = self._zone.getSettings() debugprint("Firewall data obtained") if reply_handler: reply_handler(self._fw_data) except (dbus.exceptions.DBusException, AttributeError, ValueError) as e: self._fw_data = None debugprint("Exception examining firewall") if error_handler: error_handler(e) return self._fw_data def read(self, reply_handler=None, error_handler=None): if reply_handler: self._get_fw_data(reply_handler, error_handler) else: self._get_fw_data() def write(self): try: if self._zone: self._zone.update(self._fw_data) self._fw.reload() except dbus.exceptions.DBusException: nonfatalException() def add_service(self, service): if not self._get_fw_data(): return self._fw_data.addService(service) def check_ipp_client_allowed(self): if not self._get_fw_data(): return True return (IPP_CLIENT_SERVICE in self._fw_data.getServices() or [IPP_CLIENT_PORT, IPP_CLIENT_PROTOCOL ] in self._fw_data.getPorts()) def check_ipp_server_allowed(self): if not self._get_fw_data(): return True return (IPP_SERVER_SERVICE in self._fw_data.getServices() or [IPP_SERVER_PORT, IPP_SERVER_PROTOCOL ] in self._fw_data.getPorts()) def check_samba_client_allowed(self): if not self._get_fw_data(): return True return (SAMBA_CLIENT_SERVICE in self._fw_data.getServices()) def check_mdns_allowed(self): if not self._get_fw_data(): return True return (MDNS_SERVICE in self._fw_data.getServices() or [MDNS_PORT, MDNS_PROTOCOL] in self._fw_data.getPorts())
class TestFirewallDInterfaceConfig(unittest.TestCase): """ For testing of permanent changes, ie. those that survive restart: """ def setUp(self): unittest.TestCase.setUp(self) self.fw = FirewallClient() def tearDown(self): unittest.TestCase.tearDown(self) def test_zones(self): """ /org/fedoraproject/FirewallD1/config listZones() getZoneByName(String name) addZone(String name, Dict of {String, Variant} zone_settings) /org/fedoraproject/FirewallD1/config/zone/<id> getSettings() loadDefaults() update() rename() remove() """ print ("\nGetting invalid zone") self.assertRaisesRegexp(Exception, 'INVALID_ZONE', self.fw.config().getZoneByName, "dummyname") zone_version = "1.0" zone_short = "Testing" zone_description = "this is just a testing zone" zone_target = DEFAULT_ZONE_TARGET zone_services = ["dhcpv6-client", "ssh"] zone_ports = [("123", "tcp"), ("666-667", "udp")] zone_icmpblocks = ["redirect", "echo-reply"] zone_masquerade = False zone_forward_ports = [("443", "tcp", "441", "192.168.0.2"), ("123", "udp", "321", "192.168.1.1")] settings = FirewallClientZoneSettings() settings.setVersion(zone_version) settings.setShort(zone_short) settings.setDescription(zone_description) settings.setTarget(zone_target) settings.setServices(zone_services) settings.setPorts(zone_ports) settings.setIcmpBlocks(zone_icmpblocks) settings.setMasquerade(zone_masquerade) settings.setForwardPorts(zone_forward_ports) print ("Adding zone with name that already exists") self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', self.fw.config().addZone, "home", settings) print ("Adding zone with empty name") self.assertRaisesRegexp(Exception, 'INVALID_NAME', self.fw.config().addZone, "", settings) zone_name = "test" print ("Adding proper zone") self.fw.config().addZone (zone_name, settings) print ("Checking the saved (permanent) settings") config_zone = self.fw.config().getZoneByName(zone_name) self.assertIsInstance(config_zone, firewall.client.FirewallClientConfigZone) zone_settings = config_zone.getSettings() self.assertIsInstance(zone_settings, firewall.client.FirewallClientZoneSettings) self.assertEquals(zone_settings.getVersion(), zone_version) self.assertEquals(zone_settings.getShort(), zone_short) self.assertEquals(zone_settings.getDescription(), zone_description) self.assertEquals(zone_settings.getTarget(), zone_target) self.assertEquals(zone_settings.getServices().sort(), zone_services.sort()) self.assertEquals(zone_settings.getPorts().sort(), zone_ports.sort()) self.assertEquals(zone_settings.getIcmpBlocks().sort(), zone_icmpblocks.sort()) self.assertEquals(zone_settings.getMasquerade(), zone_masquerade) self.assertEquals(zone_settings.getForwardPorts().sort(), zone_forward_ports.sort()) print ("Updating settings") zone_services.append("mdns") zone_settings.setServices(zone_services) config_zone.update(zone_settings) print ("Reloading firewalld") self.fw.reload() print ("Checking of runtime settings") self.assertTrue(zone_name in self.fw.getZones()) self.assertEquals(self.fw.getServices(zone_name).sort(), zone_services.sort()) self.assertEquals(self.fw.getPorts(zone_name).sort(), zone_ports.sort()) self.assertEquals(self.fw.getIcmpBlocks(zone_name).sort(), zone_icmpblocks.sort()) self.assertEquals(self.fw.queryMasquerade(zone_name), zone_masquerade) self.assertEquals(self.fw.getForwardPorts(zone_name).sort(), zone_forward_ports.sort()) print ("Renaming zone to name that already exists") config_zone = self.fw.config().getZoneByName(zone_name) self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', config_zone.rename, "home") new_zone_name = "renamed" print ("Renaming zone '%s' to '%s'" % (zone_name, new_zone_name)) config_zone.rename(new_zone_name) print ("Checking whether the zone '%s' is accessible (it shouldn't be)" % zone_name) self.assertRaisesRegexp(Exception, 'INVALID_ZONE', self.fw.config().getZoneByName, zone_name) print ("Checking whether the zone '%s' is accessible" % new_zone_name) config_zone = self.fw.config().getZoneByName(new_zone_name) zone_settings = config_zone.getSettings() self.assertEquals(zone_settings.getVersion(), zone_version) self.assertEquals(zone_settings.getShort(), zone_short) self.assertEquals(zone_settings.getDescription(), zone_description) self.assertEquals(zone_settings.getTarget(), zone_target) self.assertEquals(zone_settings.getServices().sort(), zone_services.sort()) self.assertEquals(zone_settings.getPorts().sort(), zone_ports.sort()) self.assertEquals(zone_settings.getIcmpBlocks().sort(), zone_icmpblocks.sort()) self.assertEquals(zone_settings.getMasquerade(), zone_masquerade) self.assertEquals(zone_settings.getForwardPorts().sort(), zone_forward_ports.sort()) print ("Removing the zone '%s'" % new_zone_name) config_zone.remove() print ("Checking whether the removed zone is accessible (it shouldn't be)") self.assertRaisesRegexp(Exception, 'INVALID_ZONE', self.fw.config().getZoneByName, new_zone_name) # TODO test loadDefaults() ? def test_services(self): """ /org/fedoraproject/FirewallD1/config listServices() getServiceByName(String name) addService(String name, Dict of {String, Variant} settings) /org/fedoraproject/FirewallD1/config/service/<id> getSettings() loadDefaults() update() rename() remove() """ print ("\nGetting invalid service") self.assertRaisesRegexp(Exception, 'INVALID_SERVICE', self.fw.config().getServiceByName, "dummyname") service_version = "1.0" service_short = "Testing" service_description = "this is just a testing service" service_ports = [("123", "tcp"), ("666-667", "udp")] service_modules = ["nf_test_first", "nf_test_second"] service_destinations = {'ipv4': '1.2.3.4', 'ipv6': 'dead::beef'} settings = FirewallClientServiceSettings() # ["", "", "", [], [], {}] settings.setVersion(service_version) settings.setShort(service_short) settings.setDescription(service_description) settings.setPorts(service_ports) settings.setModules(service_modules) settings.setDestinations(service_destinations) print ("Adding service with name that already exists") self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', self.fw.config().addService, "mdns", settings) print ("Adding service with empty name") self.assertRaisesRegexp(Exception, 'INVALID_NAME', self.fw.config().addService, "", settings) service_name = "test" print ("Adding proper service") self.fw.config().addService (service_name, settings) print ("Checking the saved (permanent) settings") config_service = self.fw.config().getServiceByName(service_name) self.assertIsInstance(config_service, firewall.client.FirewallClientConfigService) service_settings = config_service.getSettings() self.assertIsInstance(service_settings, firewall.client.FirewallClientServiceSettings) print ("Updating settings") service_modules.append("nf_test_third") service_destinations["ipv6"] = "3ffe:501:ffff::" service_settings.setModules(service_modules) service_settings.setDestinations(service_destinations) config_service.update(service_settings) self.assertEquals(service_settings.getVersion(), service_version) self.assertEquals(service_settings.getShort(), service_short) self.assertEquals(service_settings.getDescription(), service_description) self.assertEquals(service_settings.getPorts().sort(), service_ports.sort()) self.assertEquals(service_settings.getModules().sort(), service_modules.sort()) self.assertDictEqual(service_settings.getDestinations(), service_destinations) print ("Renaming service to name that already exists") config_service = self.fw.config().getServiceByName(service_name) self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', config_service.rename, "mdns") new_service_name = "renamed" print ("Renaming service '%s' to '%s'" % (service_name, new_service_name)) config_service.rename(new_service_name) print ("Checking whether the service '%s' is accessible (it shouldn't be)" % service_name) self.assertRaisesRegexp(Exception, 'INVALID_SERVICE', self.fw.config().getServiceByName, service_name) print ("Checking whether the service '%s' is accessible" % new_service_name) config_service = self.fw.config().getServiceByName(new_service_name) service_settings = config_service.getSettings() self.assertEquals(service_settings.getVersion(), service_version) self.assertEquals(service_settings.getShort(), service_short) self.assertEquals(service_settings.getDescription(), service_description) self.assertEquals(service_settings.getPorts().sort(), service_ports.sort()) self.assertEquals(service_settings.getModules().sort(), service_modules.sort()) self.assertDictEqual(service_settings.getDestinations(), service_destinations) print ("Removing the service '%s'" % new_service_name) config_service.remove() print ("Checking whether the removed service is accessible (it shouldn't be)") self.assertRaisesRegexp(Exception, 'INVALID_SERVICE', self.fw.config().getServiceByName, new_service_name) # TODO test loadDefaults() ? def test_icmptypes(self): """ /org/fedoraproject/FirewallD1/config listIcmpTypes() getIcmpTypeByName(String name) addIcmpType(String name, Dict of {String, Variant} settings) /org/fedoraproject/FirewallD1/config/icmptype/<id> getSettings() loadDefaults() update() rename() remove() """ print ("\nGetting invalid icmp-type") self.assertRaisesRegexp(Exception, 'INVALID_ICMPTYPE', self.fw.config().getIcmpTypeByName, "dummyname") icmptype_version = "1.0" icmptype_short = "Testing" icmptype_description = "this is just a testing icmp type" icmptype_destinations = ['ipv4'] settings = FirewallClientIcmpTypeSettings() # ["", "", "", []] settings.setVersion(icmptype_version) settings.setShort(icmptype_short) settings.setDescription(icmptype_description) settings.setDestinations(icmptype_destinations) print ("Adding icmp type with name that already exists") self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', self.fw.config().addIcmpType, "echo-reply", settings) print ("Adding icmp type with empty name") self.assertRaisesRegexp(Exception, 'INVALID_NAME', self.fw.config().addIcmpType, "", settings) icmptype_name = "test" print ("Adding proper icmp type") self.fw.config().addIcmpType (icmptype_name, settings) print ("Checking the saved (permanent) settings") config_icmptype = self.fw.config().getIcmpTypeByName(icmptype_name) self.assertIsInstance(config_icmptype, firewall.client.FirewallClientConfigIcmpType) icmptype_settings = config_icmptype.getSettings() self.assertIsInstance(icmptype_settings, firewall.client.FirewallClientIcmpTypeSettings) print ("Updating settings") icmptype_destinations.append("ipv6") icmptype_settings.setDestinations(icmptype_destinations) config_icmptype.update(icmptype_settings) self.assertEquals(icmptype_settings.getVersion(), icmptype_version) self.assertEquals(icmptype_settings.getShort(), icmptype_short) self.assertEquals(icmptype_settings.getDescription(), icmptype_description) self.assertEquals(icmptype_settings.getDestinations().sort(), icmptype_destinations.sort()) print ("Renaming icmp type to name that already exists") config_icmptype = self.fw.config().getIcmpTypeByName(icmptype_name) self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', config_icmptype.rename, "echo-reply") new_icmptype_name = "renamed" print ("Renaming icmp type '%s' to '%s'" % (icmptype_name, new_icmptype_name)) config_icmptype.rename(new_icmptype_name) print ("Checking whether the icmp type '%s' is accessible (it shouldn't be)" % icmptype_name) self.assertRaisesRegexp(Exception, 'INVALID_ICMPTYPE', self.fw.config().getIcmpTypeByName, icmptype_name) print ("Checking whether the icmp type '%s' is accessible" % new_icmptype_name) config_icmptype = self.fw.config().getIcmpTypeByName(new_icmptype_name) icmptype_settings = config_icmptype.getSettings() self.assertEquals(icmptype_settings.getVersion(), icmptype_version) self.assertEquals(icmptype_settings.getShort(), icmptype_short) self.assertEquals(icmptype_settings.getDescription(), icmptype_description) self.assertEquals(icmptype_settings.getDestinations().sort(), icmptype_destinations.sort()) print ("Removing the icmp type '%s'" % new_icmptype_name) config_icmptype.remove() print ("Checking whether the removed icmp type is accessible (it shouldn't be)") self.assertRaisesRegexp(Exception, 'INVALID_ICMPTYPE', self.fw.config().getIcmpTypeByName, new_icmptype_name)
class TestFirewallDInterfaceConfig(unittest.TestCase): """ For testing of permanent changes, ie. those that survive restart: """ def setUp(self): unittest.TestCase.setUp(self) self.fw = FirewallClient() def tearDown(self): unittest.TestCase.tearDown(self) def test_zones(self): """ /org/fedoraproject/FirewallD1/config listZones() getZoneByName(String name) addZone(String name, Dict of {String, Variant} zone_settings) /org/fedoraproject/FirewallD1/config/zone/<id> getSettings() loadDefaults() update() rename() remove() """ print("\nGetting invalid zone") self.assertRaisesRegexp(Exception, 'INVALID_ZONE', self.fw.config().getZoneByName, "dummyname") zone_version = "1.0" zone_short = "Testing" zone_description = "this is just a testing zone" zone_target = DEFAULT_ZONE_TARGET zone_services = ["dhcpv6-client", "ssh"] zone_ports = [("123", "tcp"), ("666-667", "udp")] zone_icmpblocks = ["redirect", "echo-reply"] zone_masquerade = False zone_forward_ports = [("443", "tcp", "441", "192.168.0.2"), ("123", "udp", "321", "192.168.1.1")] settings = FirewallClientZoneSettings() settings.setVersion(zone_version) settings.setShort(zone_short) settings.setDescription(zone_description) settings.setTarget(zone_target) settings.setServices(zone_services) settings.setPorts(zone_ports) settings.setIcmpBlocks(zone_icmpblocks) settings.setMasquerade(zone_masquerade) settings.setForwardPorts(zone_forward_ports) print("Adding zone with name that already exists") self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', self.fw.config().addZone, "home", settings) print("Adding zone with empty name") self.assertRaisesRegexp(Exception, 'INVALID_NAME', self.fw.config().addZone, "", settings) zone_name = "test" print("Adding proper zone") self.fw.config().addZone(zone_name, settings) print("Checking the saved (permanent) settings") config_zone = self.fw.config().getZoneByName(zone_name) self.assertIsInstance(config_zone, firewall.client.FirewallClientConfigZone) zone_settings = config_zone.getSettings() self.assertIsInstance(zone_settings, firewall.client.FirewallClientZoneSettings) self.assertEquals(zone_settings.getVersion(), zone_version) self.assertEquals(zone_settings.getShort(), zone_short) self.assertEquals(zone_settings.getDescription(), zone_description) self.assertEquals(zone_settings.getTarget(), "default") self.assertEquals(zone_settings.getServices().sort(), zone_services.sort()) self.assertEquals(zone_settings.getPorts().sort(), zone_ports.sort()) self.assertEquals(zone_settings.getIcmpBlocks().sort(), zone_icmpblocks.sort()) self.assertEquals(zone_settings.getMasquerade(), zone_masquerade) self.assertEquals(zone_settings.getForwardPorts().sort(), zone_forward_ports.sort()) print("Updating settings") zone_services.append("mdns") zone_settings.setServices(zone_services) config_zone.update(zone_settings) print("Reloading firewalld") self.fw.reload() print("Checking of runtime settings") self.assertTrue(zone_name in self.fw.getZones()) self.assertEquals( self.fw.getServices(zone_name).sort(), zone_services.sort()) self.assertEquals( self.fw.getPorts(zone_name).sort(), zone_ports.sort()) self.assertEquals( self.fw.getIcmpBlocks(zone_name).sort(), zone_icmpblocks.sort()) self.assertEquals(self.fw.queryMasquerade(zone_name), zone_masquerade) self.assertEquals( self.fw.getForwardPorts(zone_name).sort(), zone_forward_ports.sort()) print("Renaming zone to name that already exists") config_zone = self.fw.config().getZoneByName(zone_name) self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', config_zone.rename, "home") new_zone_name = "renamed" print("Renaming zone '%s' to '%s'" % (zone_name, new_zone_name)) config_zone.rename(new_zone_name) print( "Checking whether the zone '%s' is accessible (it shouldn't be)" % zone_name) self.assertRaisesRegexp(Exception, 'INVALID_ZONE', self.fw.config().getZoneByName, zone_name) print("Checking whether the zone '%s' is accessible" % new_zone_name) config_zone = self.fw.config().getZoneByName(new_zone_name) zone_settings = config_zone.getSettings() self.assertEquals(zone_settings.getVersion(), zone_version) self.assertEquals(zone_settings.getShort(), zone_short) self.assertEquals(zone_settings.getDescription(), zone_description) self.assertEquals(zone_settings.getTarget(), "default") self.assertEquals(zone_settings.getServices().sort(), zone_services.sort()) self.assertEquals(zone_settings.getPorts().sort(), zone_ports.sort()) self.assertEquals(zone_settings.getIcmpBlocks().sort(), zone_icmpblocks.sort()) self.assertEquals(zone_settings.getMasquerade(), zone_masquerade) self.assertEquals(zone_settings.getForwardPorts().sort(), zone_forward_ports.sort()) print("Removing the zone '%s'" % new_zone_name) config_zone.remove() print( "Checking whether the removed zone is accessible (it shouldn't be)" ) self.assertRaisesRegexp(Exception, 'INVALID_ZONE', self.fw.config().getZoneByName, new_zone_name) # TODO test loadDefaults() ? def test_services(self): """ /org/fedoraproject/FirewallD1/config listServices() getServiceByName(String name) addService(String name, Dict of {String, Variant} settings) /org/fedoraproject/FirewallD1/config/service/<id> getSettings() loadDefaults() update() rename() remove() """ print("\nGetting invalid service") self.assertRaisesRegexp(Exception, 'INVALID_SERVICE', self.fw.config().getServiceByName, "dummyname") service_version = "1.0" service_short = "Testing" service_description = "this is just a testing service" service_ports = [("123", "tcp"), ("666-667", "udp")] service_modules = ["nf_conntrack_tftp"] service_destinations = {'ipv4': '1.2.3.4', 'ipv6': 'dead::beef'} settings = FirewallClientServiceSettings() # ["", "", "", [], [], {}] settings.setVersion(service_version) settings.setShort(service_short) settings.setDescription(service_description) settings.setPorts(service_ports) settings.setModules(service_modules) settings.setDestinations(service_destinations) print("Adding service with name that already exists") self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', self.fw.config().addService, "mdns", settings) print("Adding service with empty name") self.assertRaisesRegexp(Exception, 'INVALID_NAME', self.fw.config().addService, "", settings) service_name = "test" print("Adding proper service") self.fw.config().addService(service_name, settings) print("Checking the saved (permanent) settings") config_service = self.fw.config().getServiceByName(service_name) self.assertIsInstance(config_service, firewall.client.FirewallClientConfigService) service_settings = config_service.getSettings() self.assertIsInstance(service_settings, firewall.client.FirewallClientServiceSettings) print("Updating settings") service_modules.append("nf_conntrack_sip") service_destinations["ipv6"] = "3ffe:501:ffff::" service_settings.setModules(service_modules) service_settings.setDestinations(service_destinations) config_service.update(service_settings) self.assertEquals(service_settings.getVersion(), service_version) self.assertEquals(service_settings.getShort(), service_short) self.assertEquals(service_settings.getDescription(), service_description) self.assertEquals(service_settings.getPorts().sort(), service_ports.sort()) self.assertEquals(service_settings.getModules().sort(), service_modules.sort()) self.assertDictEqual(service_settings.getDestinations(), service_destinations) print("Renaming service to name that already exists") config_service = self.fw.config().getServiceByName(service_name) self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', config_service.rename, "mdns") new_service_name = "renamed" print("Renaming service '%s' to '%s'" % (service_name, new_service_name)) config_service.rename(new_service_name) print( "Checking whether the service '%s' is accessible (it shouldn't be)" % service_name) self.assertRaisesRegexp(Exception, 'INVALID_SERVICE', self.fw.config().getServiceByName, service_name) print("Checking whether the service '%s' is accessible" % new_service_name) config_service = self.fw.config().getServiceByName(new_service_name) service_settings = config_service.getSettings() self.assertEquals(service_settings.getVersion(), service_version) self.assertEquals(service_settings.getShort(), service_short) self.assertEquals(service_settings.getDescription(), service_description) self.assertEquals(service_settings.getPorts().sort(), service_ports.sort()) self.assertEquals(service_settings.getModules().sort(), service_modules.sort()) self.assertDictEqual(service_settings.getDestinations(), service_destinations) print("Removing the service '%s'" % new_service_name) config_service.remove() print( "Checking whether the removed service is accessible (it shouldn't be)" ) self.assertRaisesRegexp(Exception, 'INVALID_SERVICE', self.fw.config().getServiceByName, new_service_name) # TODO test loadDefaults() ? def test_icmptypes(self): """ /org/fedoraproject/FirewallD1/config listIcmpTypes() getIcmpTypeByName(String name) addIcmpType(String name, Dict of {String, Variant} settings) /org/fedoraproject/FirewallD1/config/icmptype/<id> getSettings() loadDefaults() update() rename() remove() """ print("\nGetting invalid icmp-type") self.assertRaisesRegexp(Exception, 'INVALID_ICMPTYPE', self.fw.config().getIcmpTypeByName, "dummyname") icmptype_version = "1.0" icmptype_short = "Testing" icmptype_description = "this is just a testing icmp type" icmptype_destinations = ['ipv4'] settings = FirewallClientIcmpTypeSettings() # ["", "", "", []] settings.setVersion(icmptype_version) settings.setShort(icmptype_short) settings.setDescription(icmptype_description) settings.setDestinations(icmptype_destinations) print("Adding icmp type with name that already exists") self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', self.fw.config().addIcmpType, "echo-reply", settings) print("Adding icmp type with empty name") self.assertRaisesRegexp(Exception, 'INVALID_NAME', self.fw.config().addIcmpType, "", settings) icmptype_name = "test" print("Adding proper icmp type") self.fw.config().addIcmpType(icmptype_name, settings) print("Checking the saved (permanent) settings") config_icmptype = self.fw.config().getIcmpTypeByName(icmptype_name) self.assertIsInstance(config_icmptype, firewall.client.FirewallClientConfigIcmpType) icmptype_settings = config_icmptype.getSettings() self.assertIsInstance(icmptype_settings, firewall.client.FirewallClientIcmpTypeSettings) print("Updating settings") icmptype_destinations.append("ipv6") icmptype_settings.setDestinations(icmptype_destinations) config_icmptype.update(icmptype_settings) self.assertEquals(icmptype_settings.getVersion(), icmptype_version) self.assertEquals(icmptype_settings.getShort(), icmptype_short) self.assertEquals(icmptype_settings.getDescription(), icmptype_description) self.assertEquals(icmptype_settings.getDestinations().sort(), icmptype_destinations.sort()) print("Renaming icmp type to name that already exists") config_icmptype = self.fw.config().getIcmpTypeByName(icmptype_name) self.assertRaisesRegexp(Exception, 'NAME_CONFLICT', config_icmptype.rename, "echo-reply") new_icmptype_name = "renamed" print("Renaming icmp type '%s' to '%s'" % (icmptype_name, new_icmptype_name)) config_icmptype.rename(new_icmptype_name) print( "Checking whether the icmp type '%s' is accessible (it shouldn't be)" % icmptype_name) self.assertRaisesRegexp(Exception, 'INVALID_ICMPTYPE', self.fw.config().getIcmpTypeByName, icmptype_name) print("Checking whether the icmp type '%s' is accessible" % new_icmptype_name) config_icmptype = self.fw.config().getIcmpTypeByName(new_icmptype_name) icmptype_settings = config_icmptype.getSettings() self.assertEquals(icmptype_settings.getVersion(), icmptype_version) self.assertEquals(icmptype_settings.getShort(), icmptype_short) self.assertEquals(icmptype_settings.getDescription(), icmptype_description) self.assertEquals(icmptype_settings.getDestinations().sort(), icmptype_destinations.sort()) print("Removing the icmp type '%s'" % new_icmptype_name) config_icmptype.remove() print( "Checking whether the removed icmp type is accessible (it shouldn't be)" ) self.assertRaisesRegexp(Exception, 'INVALID_ICMPTYPE', self.fw.config().getIcmpTypeByName, new_icmptype_name)
class FirewallWrapper: NETWORKBLOCK_IPSET4 = 'networkblock4' NETWORKBLOCK_IPSET6 = 'networkblock6' NETWORKBLOCK_IPSET_BASE_NAME = 'networkblock' def __init__(self): self.fw = FirewallClient() self.config = self.fw.config() if not self.config: log.warning('FirewallD is not running attempting to start...') import subprocess import time subprocess.check_output( ['systemctl', 'enable', '--now', 'firewalld']) # firewall-cmd synchronously waits for FirewallD startup subprocess.check_output(['firewall-cmd', '--state']) self.fw = FirewallClient() self.config = self.fw.config() def get_create_set(self, name, family='inet'): if name in self.config.getIPSetNames(): return self.config.getIPSetByName(name) settings = FirewallClientIPSetSettings() settings.setType('hash:net') settings.setOptions({ 'maxelem': '1000000', 'family': family, 'hashsize': '4096' }) return self.config.addIPSet(name, settings) def get_block_ipset4(self, name=None): if not name: name = FirewallWrapper.NETWORKBLOCK_IPSET_BASE_NAME name = name + '4' if name in self.config.getIPSetNames(): return self.config.getIPSetByName(name) settings = FirewallClientIPSetSettings() settings.setType('hash:net') settings.setOptions({ 'maxelem': '1000000', 'family': 'inet', 'hashsize': '4096' }) return self.config.addIPSet(name, settings) def get_block_ipset6(self, name=None): if not name: name = FirewallWrapper.NETWORKBLOCK_IPSET_BASE_NAME name = name + '6' if name in self.config.getIPSetNames(): return self.config.getIPSetByName(name) settings = FirewallClientIPSetSettings() settings.setType('hash:net') settings.setOptions({ 'maxelem': '1000000', 'family': 'inet6', 'hashsize': '4096' }) return self.config.addIPSet(name, settings) def get_block_ipset_for_ip(self, ip, name=None): if ip.version == 4: return self.get_block_ipset4(name) if ip.version == 6: return self.get_block_ipset6(name) return None @do_maybe_already_enabled def ensure_ipset_entries(self, ipset, entries): return ipset.setEntries(entries) @do_maybe_already_enabled def ensure_entry_in_ipset(self, ipset, entry): return ipset.addEntry(str(entry)) @do_maybe_already_enabled def ensure_entry_not_in_ipset(self, ipset, entry): return ipset.removeEntry(str(entry)) @do_maybe_already_enabled def ensure_block_ipset_in_drop_zone(self, ipset): # ensure that the block ipset is in drop zone: drop_zone = self.config.getZoneByName('drop') # self.config.getIPSetNames return drop_zone.addSource('ipset:{}'.format( ipset.get_property('name'))) @do_maybe_already_enabled def add_service(self, name, zone='public'): self.fw.addService(zone, name) self.fw.runtimeToPermanent() @do_maybe_already_enabled def block_ip(self, ip, ipset_name=None, reload=True): block_ipset = self.get_block_ipset_for_ip(ip, ipset_name) if not block_ipset: # TODO err: unsupported protocol raise Exception('Unsupported protocol') self.ensure_block_ipset_in_drop_zone(block_ipset) log.info('Adding IP address {} to block set {}'.format( ip, block_ipset.get_property('name'))) try: from aggregate6 import aggregate entries = [] for entry in block_ipset.getEntries(): entries.append(str(entry)) entries.append(str(ip)) block_ipset.setEntries(aggregate(entries)) except ImportError: block_ipset.addEntry(str(ip)) if reload: log.info('Reloading FirewallD to apply permanent configuration') self.fw.reload() log.info('Breaking connection with {}'.format(ip)) from subprocess import CalledProcessError, check_output, STDOUT try: check_output(["/sbin/conntrack", "-D", "-s", str(ip)], stderr=STDOUT) except CalledProcessError as e: pass def get_blocked_ips4(self, name=None): block_ipset4 = self.get_block_ipset4(name) return block_ipset4.getEntries() def get_blocked_ips6(self, name=None): block_ipset6 = self.get_block_ipset6(name) return block_ipset6.getEntries() @do_maybe_not_enabled def remove_ipset_from_zone(self, zone, ipset_name): # drop_zone.removeSource('ipset:') zone.removeSource('ipset:{}'.format(ipset_name)) @do_maybe_invalid_ipset def clear_ipset_by_name(self, ipset_name): try: # does not work: ipset.setEntries([]) self.fw.setEntries(ipset_name, []) except dbus.exceptions.DBusException: pass @do_maybe_invalid_ipset def destroy_ipset_by_name(self, name): log.info('Destroying IPSet {}'.format(name)) # firewalld up to this commit # https://github.com/firewalld/firewalld/commit/f5ed30ce71755155493e78c13fd9036be8f70fc4 # does not delete runtime ipsets, so we have to clear them :( # they are not removed from runtime as still reported by ipset -L # although they *are* removed from FirewallD if name not in self.fw.getIPSets(): return ipset = self.config.getIPSetByName(name) if ipset: self.clear_ipset_by_name(name) ipset.remove() def get_blocked_countries(self): blocked_countries = [] all_ipsets = self.fw.getIPSets() from .Countries import Countries countries = Countries() for ipset_name in all_ipsets: if ipset_name.startswith('fds-'): country_code = ipset_name.split('-')[1] if country_code in countries.names_by_code: blocked_countries.append( countries.names_by_code[country_code]) return blocked_countries def update_ipsets(self): need_reload = False all_ipsets = self.fw.getIPSets() from .Countries import Countries countries = Countries() is_tor_blocked = False for ipset_name in all_ipsets: if ipset_name.startswith('fds-tor-'): is_tor_blocked = True elif ipset_name.startswith('fds-'): country_code = ipset_name.split('-')[1] if country_code in countries.names_by_code: country_name = countries.names_by_code[country_code] country = countries.get_by_name(country_name) self.block_country(country, reload=False) need_reload = True if is_tor_blocked: self.block_tor(reload=False) need_reload = True if need_reload: self.fw.reload() return True def reset(self): drop_zone = self.config.getZoneByName('drop') self.remove_ipset_from_zone(drop_zone, self.NETWORKBLOCK_IPSET4) self.destroy_ipset_by_name(self.NETWORKBLOCK_IPSET4) self.remove_ipset_from_zone(drop_zone, self.NETWORKBLOCK_IPSET6) self.destroy_ipset_by_name(self.NETWORKBLOCK_IPSET6) all_ipsets = self.fw.getIPSets() # get any ipsets prefixed with "fds-" for ipset_name in all_ipsets: if ipset_name.startswith('fds-'): self.remove_ipset_from_zone(drop_zone, ipset_name) self.destroy_ipset_by_name(ipset_name) self.fw.reload() def block_tor(self, reload=True): log.info('Blocking Tor exit nodes') w = WebClient() tor4_exits = w.get_tor_exits(family=4) tor6_exits = w.get_tor_exits(family=6) tor4_ipset = self.get_create_set('fds-tor-4') self.ensure_ipset_entries(tor4_ipset, tor4_exits) tor6_ipset = self.get_create_set('fds-tor-6', family='inet6') self.ensure_ipset_entries(tor6_ipset, tor6_exits) self.ensure_block_ipset_in_drop_zone(tor4_ipset) self.ensure_block_ipset_in_drop_zone(tor6_ipset) if reload: log.info('Reloading FirewallD...') self.fw.reload() log.info('Done!') # while cron will do "sync" behavior" def block_country(self, country, reload=True): # print('address/netmask is invalid: %s' % sys.argv[1]) # parse out as a country log.info('Blocking {} {}'.format(country.name, country.getFlag())) # print("\N{grinning face}") # TODO get aggregated zone file, save as cache, # do diff to know which stuff was changed and add/remove blocks # https://docs.python.org/2/library/difflib.html # TODO persist info on which countries were blocked (in the config file) # then sync zones via "fds cron" # TODO conditional get test on getpagespeed.com w = WebClient() country_networks = w.get_country_networks(country=country) ipset = self.get_create_set(country.get_set_name()) self.ensure_ipset_entries(ipset, country_networks) # this is slow. setEntries is a lot faster # for network in tqdm(country_networks, unit='network', # desc='Adding {} networks to IPSet {}'.format(c.getNation(), c.get_set_name())): # log.debug(network) # fw.ensure_entry_in_ipset(ipset=ipset, entry=network) # TODO retry, timeout # this action re-adds all entries entirely # there should be "fds-<country.code>-<family>" ip set self.ensure_block_ipset_in_drop_zone(ipset) if reload: log.info('Reloading FirewallD...') self.fw.reload() log.info('Done!') # while cron will do "sync" behavior" def unblock_country(self, ip_or_country_name): # print('address/netmask is invalid: %s' % sys.argv[1]) # parse out as a country from .Countries import Countries countries = Countries() c = countries.get_by_name(ip_or_country_name) if not c: log.error( '{} does not look like a correct IP or a country name'.format( ip_or_country_name)) return False drop_zone = self.config.getZoneByName('drop') log.info('Unblocking {} {}'.format(c.name, c.getFlag())) self.remove_ipset_from_zone(drop_zone, c.get_set_name()) self.destroy_ipset_by_name(c.get_set_name()) log.info('Reloading FirewallD...') self.fw.reload() log.info('Done!') # while cron will do "sync" behavior" def unblock_ip(self, ip_or_country_name): block_ipset = self.get_block_ipset_for_ip(ip_or_country_name) if not block_ipset: # TODO err: unsupported protocol raise Exception('Unsupported protocol') log.info('Removing {} from block set {}'.format( ip_or_country_name, block_ipset.get_property('name'))) self.ensure_entry_not_in_ipset(block_ipset, ip_or_country_name) log.info('Reloading FirewallD to apply permanent configuration') self.fw.reload()