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 ())
예제 #2
0
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())
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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()