# -*- coding: utf-8 -*-
#
# Copyright (C) 2010-2016 Red Hat, Inc.
#
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__all__ = [ "Firewall_test" ]
import os.path
import sys
import copy
from firewall import config
from firewall import functions
from firewall.core.fw_icmptype import FirewallIcmpType
from firewall.core.fw_service import FirewallService
from firewall.core.fw_zone import FirewallZone
from firewall.core.fw_direct import FirewallDirect
from firewall.core.fw_config import FirewallConfig
from firewall.core.fw_policies import FirewallPolicies
from firewall.core.fw_ipset import FirewallIPSet
from firewall.core.fw_helper import FirewallHelper
from firewall.core.logger import log
from firewall.core.io.firewalld_conf import firewalld_conf
from firewall.core.io.direct import Direct
from firewall.core.io.service import service_reader
from firewall.core.io.icmptype import icmptype_reader
from firewall.core.io.zone import zone_reader, Zone
from firewall.core.io.ipset import ipset_reader
from firewall.core.ipset import IPSET_TYPES
from firewall.core.io.helper import helper_reader
from firewall import errors
from firewall.errors import FirewallError
############################################################################
#
# class Firewall
#
############################################################################
class Firewall_test(object):
def __init__(self):
self._firewalld_conf = firewalld_conf(config.FIREWALLD_CONF)
self.ip4tables_enabled = False
self.ip6tables_enabled = False
self.ebtables_enabled = False
self.ipset_enabled = False
self.ipset_supported_types = IPSET_TYPES
self.icmptype = FirewallIcmpType(self)
self.service = FirewallService(self)
self.zone = FirewallZone(self)
self.direct = FirewallDirect(self)
self.config = FirewallConfig(self)
self.policies = FirewallPolicies()
self.ipset = FirewallIPSet(self)
self.helper = FirewallHelper(self)
self.__init_vars()
def __repr__(self):
return '%s(%r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r)' % \
(self.__class__, self.ip4tables_enabled, self.ip6tables_enabled,
self.ebtables_enabled, self._state, self._panic,
self._default_zone, self._module_refcount, self._marks,
self._min_mark, self.cleanup_on_exit, self.ipv6_rpfilter_enabled,
self.ipset_enabled, self._individual_calls, self._log_denied,
self._automatic_helpers)
def __init_vars(self):
self._state = "INIT"
self._panic = False
self._default_zone = ""
self._module_refcount = { }
self._marks = [ ]
# fallback settings will be overloaded by firewalld.conf
self._min_mark = config.FALLBACK_MINIMAL_MARK
self.cleanup_on_exit = config.FALLBACK_CLEANUP_ON_EXIT
self.ipv6_rpfilter_enabled = config.FALLBACK_IPV6_RPFILTER
self._individual_calls = config.FALLBACK_INDIVIDUAL_CALLS
self._log_denied = config.FALLBACK_LOG_DENIED
self._automatic_helpers = config.FALLBACK_AUTOMATIC_HELPERS
def individual_calls(self):
return self._individual_calls
def _start(self, reload=False, complete_reload=False):
# initialize firewall
default_zone = config.FALLBACK_ZONE
# load firewalld config
log.debug1("Loading firewalld config file '%s'", config.FIREWALLD_CONF)
try:
self._firewalld_conf.read()
except Exception:
log.warning("Using fallback firewalld configuration settings.")
else:
if self._firewalld_conf.get("DefaultZone"):
default_zone = self._firewalld_conf.get("DefaultZone")
if self._firewalld_conf.get("MinimalMark"):
self._min_mark = int(self._firewalld_conf.get("MinimalMark"))
if self._firewalld_conf.get("CleanupOnExit"):
value = self._firewalld_conf.get("CleanupOnExit")
if value is not None and value.lower() in [ "no", "false" ]:
self.cleanup_on_exit = False
if self._firewalld_conf.get("Lockdown"):
value = self._firewalld_conf.get("Lockdown")
if value is not None and value.lower() in [ "yes", "true" ]:
log.debug1("Lockdown is enabled")
try:
self.policies.enable_lockdown()
except FirewallError:
# already enabled, this is probably reload
pass
if self._firewalld_conf.get("IPv6_rpfilter"):
value = self._firewalld_conf.get("IPv6_rpfilter")
if value is not None:
if value.lower() in [ "no", "false" ]:
self.ipv6_rpfilter_enabled = False
if value.lower() in [ "yes", "true" ]:
self.ipv6_rpfilter_enabled = True
if self.ipv6_rpfilter_enabled:
log.debug1("IPv6 rpfilter is enabled")
else:
log.debug1("IPV6 rpfilter is disabled")
if self._firewalld_conf.get("IndividualCalls"):
value = self._firewalld_conf.get("IndividualCalls")
if value is not None and value.lower() in [ "yes", "true" ]:
log.debug1("IndividualCalls is enabled")
self._individual_calls = True
if self._firewalld_conf.get("LogDenied"):
value = self._firewalld_conf.get("LogDenied")
if value is None or value.lower() == "no":
self._log_denied = "off"
else:
self._log_denied = value.lower()
log.debug1("LogDenied is set to '%s'", self._log_denied)
if self._firewalld_conf.get("AutomaticHelpers"):
value = self._firewalld_conf.get("AutomaticHelpers")
if value is not None:
if value.lower() in [ "no", "false" ]:
self._automatic_helpers = "no"
elif value.lower() in [ "yes", "true" ]:
self._automatic_helpers = "yes"
else:
self._automatic_helpers = value.lower()
log.debug1("AutomaticHelpers is set to '%s'",
self._automatic_helpers)
self.config.set_firewalld_conf(copy.deepcopy(self._firewalld_conf))
# load lockdown whitelist
log.debug1("Loading lockdown whitelist")
try:
self.policies.lockdown_whitelist.read()
except Exception as msg:
if self.policies.query_lockdown():
log.error("Failed to load lockdown whitelist '%s': %s",
self.policies.lockdown_whitelist.filename, msg)
else:
log.debug1("Failed to load lockdown whitelist '%s': %s",
self.policies.lockdown_whitelist.filename, msg)
# copy policies to config interface
self.config.set_policies(copy.deepcopy(self.policies))
# load ipset files
self._loader(config.FIREWALLD_IPSETS, "ipset")
self._loader(config.ETC_FIREWALLD_IPSETS, "ipset")
# load icmptype files
self._loader(config.FIREWALLD_ICMPTYPES, "icmptype")
self._loader(config.ETC_FIREWALLD_ICMPTYPES, "icmptype")
if len(self.icmptype.get_icmptypes()) == 0:
log.error("No icmptypes found.")
# load helper files
self._loader(config.FIREWALLD_HELPERS, "helper")
self._loader(config.ETC_FIREWALLD_HELPERS, "helper")
# load service files
self._loader(config.FIREWALLD_SERVICES, "service")
self._loader(config.ETC_FIREWALLD_SERVICES, "service")
if len(self.service.get_services()) == 0:
log.error("No services found.")
# load zone files
self._loader(config.FIREWALLD_ZONES, "zone")
self._loader(config.ETC_FIREWALLD_ZONES, "zone")
if len(self.zone.get_zones()) == 0:
log.fatal("No zones found.")
sys.exit(1)
# check minimum required zones
error = False
for z in [ "block", "drop", "trusted" ]:
if z not in self.zone.get_zones():
log.fatal("Zone '%s' is not available.", z)
error = True
if error:
sys.exit(1)
# check if default_zone is a valid zone
if default_zone not in self.zone.get_zones():
if "public" in self.zone.get_zones():
zone = "public"
elif "external" in self.zone.get_zones():
zone = "external"
else:
zone = "block" # block is a base zone, therefore it has to exist
log.error("Default zone '%s' is not valid. Using '%s'.",
default_zone, zone)
default_zone = zone
else:
log.debug1("Using default zone '%s'", default_zone)
# load direct rules
obj = Direct(config.FIREWALLD_DIRECT)
if os.path.exists(config.FIREWALLD_DIRECT):
log.debug1("Loading direct rules file '%s'" % \
config.FIREWALLD_DIRECT)
try:
obj.read()
except Exception as msg:
log.error("Failed to load direct rules file '%s': %s",
config.FIREWALLD_DIRECT, msg)
self.config.set_direct(copy.deepcopy(obj))
self._default_zone = self.check_zone(default_zone)
self._state = "RUNNING"
def start(self):
self._start()
def _loader(self, path, reader_type, combine=False):
# combine: several zone files are getting combined into one obj
if not os.path.isdir(path):
return
if combine:
if path.startswith(config.ETC_FIREWALLD) and reader_type == "zone":
combined_zone = Zone()
combined_zone.name = os.path.basename(path)
combined_zone.check_name(combined_zone.name)
combined_zone.path = path
combined_zone.default = False
else:
combine = False
for filename in sorted(os.listdir(path)):
if not filename.endswith(".xml"):
if path.startswith(config.ETC_FIREWALLD) and \
reader_type == "zone" and \
os.path.isdir("%s/%s" % (path, filename)):
self._loader("%s/%s" % (path, filename), reader_type,
combine=True)
continue
name = "%s/%s" % (path, filename)
log.debug1("Loading %s file '%s'", reader_type, name)
try:
if reader_type == "icmptype":
obj = icmptype_reader(filename, path)
if obj.name in self.icmptype.get_icmptypes():
orig_obj = self.icmptype.get_icmptype(obj.name)
log.debug1(" Overloads %s '%s' ('%s/%s')", reader_type,
orig_obj.name, orig_obj.path,
orig_obj.filename)
self.icmptype.remove_icmptype(orig_obj.name)
elif obj.path.startswith(config.ETC_FIREWALLD):
obj.default = True
self.icmptype.add_icmptype(obj)
# add a deep copy to the configuration interface
self.config.add_icmptype(copy.deepcopy(obj))
elif reader_type == "service":
obj = service_reader(filename, path)
if obj.name in self.service.get_services():
orig_obj = self.service.get_service(obj.name)
log.debug1(" Overloads %s '%s' ('%s/%s')", reader_type,
orig_obj.name, orig_obj.path,
orig_obj.filename)
self.service.remove_service(orig_obj.name)
elif obj.path.startswith(config.ETC_FIREWALLD):
obj.default = True
self.service.add_service(obj)
# add a deep copy to the configuration interface
self.config.add_service(copy.deepcopy(obj))
elif reader_type == "zone":
obj = zone_reader(filename, path, no_check_name=combine)
if combine:
# Change name for permanent configuration
obj.name = "%s/%s" % (
os.path.basename(path),
os.path.basename(filename)[0:-4])
obj.check_name(obj.name)
# Copy object before combine
config_obj = copy.deepcopy(obj)
if obj.name in self.zone.get_zones():
orig_obj = self.zone.get_zone(obj.name)
self.zone.remove_zone(orig_obj.name)
if orig_obj.combined:
log.debug1(" Combining %s '%s' ('%s/%s')",
reader_type, obj.name,
path, filename)
obj.combine(orig_obj)
else:
log.debug1(" Overloads %s '%s' ('%s/%s')",
reader_type,
orig_obj.name, orig_obj.path,
orig_obj.filename)
elif obj.path.startswith(config.ETC_FIREWALLD):
obj.default = True
config_obj.default = True
self.config.add_zone(config_obj)
if combine:
log.debug1(" Combining %s '%s' ('%s/%s')",
reader_type, combined_zone.name,
path, filename)
combined_zone.combine(obj)
else:
self.zone.add_zone(obj)
elif reader_type == "ipset":
obj = ipset_reader(filename, path)
if obj.name in self.ipset.get_ipsets():
orig_obj = self.ipset.get_ipset(obj.name)
log.debug1(" Overloads %s '%s' ('%s/%s')", reader_type,
orig_obj.name, orig_obj.path,
orig_obj.filename)
self.ipset.remove_ipset(orig_obj.name)
elif obj.path.startswith(config.ETC_FIREWALLD):
obj.default = True
self.ipset.add_ipset(obj)
# add a deep copy to the configuration interface
self.config.add_ipset(copy.deepcopy(obj))
elif reader_type == "helper":
obj = helper_reader(filename, path)
if obj.name in self.helper.get_helpers():
orig_obj = self.helper.get_helper(obj.name)
log.debug1(" Overloads %s '%s' ('%s/%s')", reader_type,
orig_obj.name, orig_obj.path,
orig_obj.filename)
self.helper.remove_helper(orig_obj.name)
elif obj.path.startswith(config.ETC_FIREWALLD):
obj.default = True
self.helper.add_helper(obj)
# add a deep copy to the configuration interface
self.config.add_helper(copy.deepcopy(obj))
else:
log.fatal("Unknown reader type %s", reader_type)
except FirewallError as msg:
log.error("Failed to load %s file '%s': %s", reader_type,
name, msg)
except Exception:
log.error("Failed to load %s file '%s':", reader_type, name)
log.exception()
if combine and combined_zone.combined:
if combined_zone.name in self.zone.get_zones():
orig_obj = self.zone.get_zone(combined_zone.name)
log.debug1(" Overloading and deactivating %s '%s' ('%s/%s')",
reader_type, orig_obj.name, orig_obj.path,
orig_obj.filename)
try:
self.zone.remove_zone(combined_zone.name)
except:
pass
self.config.forget_zone(combined_zone.name)
self.zone.add_zone(combined_zone)
def cleanup(self):
self.icmptype.cleanup()
self.service.cleanup()
self.zone.cleanup()
self.ipset.cleanup()
self.helper.cleanup()
self.config.cleanup()
self.direct.cleanup()
self.policies.cleanup()
self._firewalld_conf.cleanup()
self.__init_vars()
def stop(self):
self.cleanup()
# check functions
def check_panic(self):
return
def check_zone(self, zone):
_zone = zone
if not _zone or _zone == "":
_zone = self.get_default_zone()
if _zone not in self.zone.get_zones():
raise FirewallError(errors.INVALID_ZONE, _zone)
return _zone
def check_interface(self, interface):
if not functions.checkInterface(interface):
raise FirewallError(errors.INVALID_INTERFACE, interface)
def check_service(self, service):
self.service.check_service(service)
def check_port(self, port):
range = functions.getPortRange(port)
if range == -2 or range == -1 or range is None or \
(len(range) == 2 and range[0] >= range[1]):
if range == -2:
log.debug1("'%s': port > 65535" % port)
elif range == -1:
log.debug1("'%s': port is invalid" % port)
elif range is None:
log.debug1("'%s': port is ambiguous" % port)
elif len(range) == 2 and range[0] >= range[1]:
log.debug1("'%s': range start >= end" % port)
raise FirewallError(errors.INVALID_PORT, port)
def check_tcpudp(self, protocol):
if not protocol:
raise FirewallError(errors.MISSING_PROTOCOL)
if protocol not in [ "tcp", "udp", "sctp", "dccp" ]:
raise FirewallError(errors.INVALID_PROTOCOL,
"'%s' not in {'tcp'|'udp'|'sctp'|'dccp'}" % \
protocol)
def check_ip(self, ip):
if not functions.checkIP(ip):
raise FirewallError(errors.INVALID_ADDR, ip)
def check_address(self, ipv, source):
if ipv == "ipv4":
if not functions.checkIPnMask(source):
raise FirewallError(errors.INVALID_ADDR, source)
elif ipv == "ipv6":
if not functions.checkIP6nMask(source):
raise FirewallError(errors.INVALID_ADDR, source)
else:
raise FirewallError(errors.INVALID_IPV,
"'%s' not in {'ipv4'|'ipv6'}")
def check_icmptype(self, icmp):
self.icmptype.check_icmptype(icmp)
# RELOAD
def reload(self, stop=False):
return
# STATE
def get_state(self):
return self._state
# PANIC MODE
def enable_panic_mode(self):
return
def disable_panic_mode(self):
return
def query_panic_mode(self):
return self._panic
# LOG DENIED
def get_log_denied(self):
return self._log_denied
def set_log_denied(self, value):
if value not in config.LOG_DENIED_VALUES:
raise FirewallError(errors.INVALID_VALUE,
"'%s', choose from '%s'" % \
(value, "','".join(config.LOG_DENIED_VALUES)))
if value != self.get_log_denied():
self._log_denied = value
self._firewalld_conf.set("LogDenied", value)
self._firewalld_conf.write()
# now reload the firewall
self.reload()
else:
raise FirewallError(errors.ALREADY_SET, value)
# AUTOMATIC HELPERS
def get_automatic_helpers(self):
return self._automatic_helpers
def set_automatic_helpers(self, value):
if value not in config.AUTOMATIC_HELPERS_VALUES:
raise FirewallError(errors.INVALID_VALUE,
"'%s', choose from '%s'" % \
(value, "','".join(config.AUTOMATIC_HELPERS_VALUES)))
if value != self.get_automatic_helpers():
self._automatic_helpers = value
self._firewalld_conf.set("AutomaticHelpers", value)
self._firewalld_conf.write()
# now reload the firewall
self.reload()
else:
raise FirewallError(errors.ALREADY_SET, value)
# DEFAULT ZONE
def get_default_zone(self):
return self._default_zone
def set_default_zone(self, zone):
_zone = self.check_zone(zone)
if _zone != self._default_zone:
self._default_zone = _zone
self._firewalld_conf.set("DefaultZone", _zone)
self._firewalld_conf.write()
else:
raise FirewallError(errors.ZONE_ALREADY_SET, _zone)
# lockdown
def enable_lockdown(self):
self._firewalld_conf.set("Lockdown", "yes")
self._firewalld_conf.write()
def disable_lockdown(self):
self._firewalld_conf.set("Lockdown", "no")
self._firewalld_conf.write()