class Main(object): """ TODO: Doku """ def __init__(self, debug=False): APP.secret_key = os.urandom(12) self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(loglevel, to_stdout, to_files, logpath, logfile) info("starting easywall-web") self.is_first_run = not folder_exists("rules") self.rules_handler = RulesHandler() if self.is_first_run: self.rules_handler.rules_firstrun() if debug is True: port = self.cfg.get_value("WEB", "bindport") host = self.cfg.get_value("WEB", "bindip") APP.run(host, port, debug)
def __init__(self, debug: bool = False) -> None: self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(str(loglevel), bool(to_stdout), bool(to_files), str(logpath), str(logfile)) self.login_attempts = self.cfg.get_value("WEB", "login_attempts") self.login_bantime = self.cfg.get_value("WEB", "login_bantime") self.ip_ban = IpBan(app=APP, ban_count=self.login_attempts, ban_seconds=self.login_bantime, ipc=True) self.ip_ban.url_pattern_add('^/static.*$', match_type='regex') info("starting easywall-web") self.rules_handler = RulesHandler() self.rules_handler.ensure_files_exist() if debug is True: port = self.cfg.get_value("WEB", "bindport") host = self.cfg.get_value("WEB", "bindip") APP.config.from_object('easywall_web.__main__.DevelopmentConfig') APP.run(str(host), str(port)) else: APP.config.from_object('easywall_web.__main__.ProductionConfig')
def __init__(self, debug: bool = False) -> None: self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(str(loglevel), bool(to_stdout), bool(to_files), str(logpath), str(logfile)) info("starting easywall-web") self.is_first_run = not folder_exists("rules") self.rules_handler = RulesHandler() if self.is_first_run: self.rules_handler.ensure_files_exist() if debug is True: port = self.cfg.get_value("WEB", "bindport") host = self.cfg.get_value("WEB", "bindip") APP.config.from_object('easywall_web.__main__.DevelopmentConfig') APP.run(str(host), str(port)) else: APP.config.from_object('easywall_web.__main__.ProductionConfig')
def remove_port(port: str, ruletype: str) -> None: """ The function deletes a port from the list of open ports. """ rules = RulesHandler() rulelist = rules.get_rules_for_web(ruletype) rulelist.remove(port) rules.save_new_rules(ruletype, rulelist)
def add_port(port: str, ruletype: str) -> None: """ The function adds a port to the list of open ports. """ rules = RulesHandler() rulelist = rules.get_rules_for_web(ruletype) rulelist.append(port) rules.save_new_rules(ruletype, rulelist)
def remove_forwarding(source_port: str, dest_port: str, ruletype: str) -> None: """ TODO: Docu """ rules = RulesHandler() rulelist = rules.get_rules_for_web("forwarding") rulelist.remove("{}:{}:{}".format(ruletype, source_port, dest_port)) rules.save_new_rules("forwarding", rulelist)
def __init__(self, config: Config) -> None: self.cfg = config self.iptables = Iptables(self.cfg) self.acceptance = Acceptance(self.cfg) self.ipv6 = self.cfg.get_value("IPV6", "enabled") self.filepath = None self.filename = None self.date = None self.rules = RulesHandler()
def remove_port(entry: dict) -> bool: """Delete a port from the list of open ports.""" rules = RulesHandler() rulelist = rules.get_rules_for_web(entry["ruletype"]) for i in range(len(rulelist)): if rulelist[i]['port'] == entry["port"]: del rulelist[i] break rules.save_new_rules(entry["ruletype"], rulelist) return True
def custom_save() -> str: """the function saves the custom rules into the corresponding rulesfile""" utils = Webutils() rules = RulesHandler() if utils.check_login(request) is True: for key, value in request.form.items(): key = str(key) + "" # just for ignoring the warning rulelist = value.split("\n") rules.save_new_rules("custom", rulelist) return custom(True) return login()
def whitelist(saved=False): """the function returns the whitelist page when the user is logged in""" utils = Webutils() rules = RulesHandler() if utils.check_login() is True: payload = utils.get_default_payload("Whitelist") payload.addresses = rules.get_rules_for_web("whitelist") payload.custom = rules.diff_new_current("whitelist") payload.saved = saved return render_template('whitelist.html', vars=payload) return login("", None)
def ports(saved=False): """the function returns the ports page when the user is logged in""" utils = Webutils() rules = RulesHandler() if utils.check_login() is True: payload = utils.get_default_payload("Ports") payload.tcp = rules.get_rules_for_web("tcp") payload.udp = rules.get_rules_for_web("udp") payload.custom = False if rules.diff_new_current("tcp") is True or rules.diff_new_current( "udp") is True: payload.custom = True payload.saved = saved return render_template('ports.html', vars=payload) return login("", None)
def add_port(entry: dict) -> bool: """Add a port to the list of open ports.""" rules = RulesHandler() ruletype = entry["ruletype"] rulelist = rules.get_rules_for_web(ruletype) entry.pop("ruletype", None) # we dont't want the ruletype to be saved duplicate = False for i in range(len(rulelist)): if rulelist[i]['port'] == entry["port"]: duplicate = True break if duplicate is False: rulelist.append(entry) rules.save_new_rules(ruletype, rulelist) return True return False
def __init__(self, debug: bool = False) -> None: """TODO: Doku.""" self.cfg_log = Config(LOG_CONFIG_PATH) loglevel = self.cfg_log.get_value("LOG", "level") to_stdout = self.cfg_log.get_value("LOG", "to_stdout") to_files = self.cfg_log.get_value("LOG", "to_files") logpath = self.cfg_log.get_value("LOG", "filepath") logfile = self.cfg_log.get_value("LOG", "filename") self.log = Log(str(loglevel), bool(to_stdout), bool(to_files), str(logpath), str(logfile)) info("starting easywall-web") self.cfg = Config(CONFIG_PATH) if debug is True: info("loading Flask debug configuration") APP.config.from_object('easywall.web.__main__.DevelopmentConfig') else: info("loading Flask production configuration") APP.config.from_object('easywall.web.__main__.ProductionConfig') self.rules_handler = RulesHandler() self.login_attempts = self.cfg.get_value("WEB", "login_attempts") self.login_bantime = self.cfg.get_value("WEB", "login_bantime") self.ip_ban = IpBan(app=APP, ban_count=self.login_attempts, ban_seconds=self.login_bantime, ipc=True) self.ip_ban.url_pattern_add('^/static.*$', match_type='regex')
def blacklist(saved: bool = False) -> str: """the function returns the blacklist page when the user is logged in""" utils = Webutils() rules = RulesHandler() if utils.check_login(request) is True: payload = utils.get_default_payload("Blacklist") payload.lead = """ On this page you can list IP addresses that are not allowed to connect to this machine. <br /> Please check the IP addresses carefully, as they are not checked by easywall.<br /> You can add IPv4 and IPv6 addresses to the list. """ payload.addresses = rules.get_rules_for_web("blacklist") payload.custom = rules.diff_new_current("blacklist") payload.saved = saved return render_template('blacklist.html', vars=payload) return login()
def custom(saved: bool = False) -> str: """the function returns the custom rules page when the user is logged in""" utils = Webutils() rules = RulesHandler() if utils.check_login(request) is True: payload = utils.get_default_payload("Custom") payload.lead = """ On this page you can add your own firewall rules.<br /> Please check the rules for accuracy, as these are not tested by easywall.<br /> <br /> To add your own rule, simply copy the rule into the text box. One rule per line.<br /> It is important to omit the iptables command.<br /> Example: <code>-P FORWARD DROP</code> """ payload.rules = rules.get_rules_for_web("custom") payload.custom = rules.diff_new_current("custom") payload.saved = saved return render_template('custom.html', vars=payload) return login()
def forwarding(saved: bool = False) -> str: """TODO: Doku.""" utils = Webutils() rules = RulesHandler() if utils.check_login(request): payload = utils.get_default_payload("Port Forwarding") payload.lead = """ This page allows you to forward ports from the local system to ports on the Internet.<br /> This is especially useful if the port of an application cannot be changed.<br /> Enter the port type, source and destination.<br /> You do not have to release the public port separately, easywall will do that for you. """ payload.forwardings = rules.get_rules_for_web("forwarding") payload.custom = False if rules.diff_new_current("forwarding"): payload.custom = True payload.saved = saved return render_template('forwarding.html', vars=payload) return login()
def ports(saved: bool = False) -> str: """Return the ports page when the user is logged in.""" utils = Webutils() rules = RulesHandler() if utils.check_login(request) is True: payload = utils.get_default_payload("Open Ports") payload.lead = """ On this page you can open ports for incoming connections.<br /> You can add tcp and udp ports.<br /> Please check whether the entries in the list are needed in the future and remove old entries if they are no longer needed.<br /> To list all open ports under Linux use the command <code>netstat -ln</code> """ payload.tcp = natsorted(rules.get_rules_for_web("tcp"), key=itemgetter(*['port'])) payload.udp = natsorted(rules.get_rules_for_web("udp"), key=itemgetter(*['port'])) payload.custom = False if rules.diff_new_current("tcp") is True or rules.diff_new_current("udp") is True: payload.custom = True payload.saved = saved return render_template('ports.html', vars=payload) return login()
def __init__(self) -> None: self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(str(loglevel), bool(to_stdout), bool(to_files), str(logpath), str(logfile)) info("starting easywall") self.rules_handler = RulesHandler() self.rules_handler.ensure_files_exist() self.easywall = Easywall(self.cfg) self.event_handler = ModifiedHandler(self.apply) self.observer = Observer() self.stop_flag = False info("easywall has been started")
def __init__(self): self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(loglevel, to_stdout, to_files, logpath, logfile) info("starting easywall") self.is_first_run = not folder_exists("rules") self.rules_handler = RulesHandler() if self.is_first_run: self.rules_handler.rules_firstrun() self.easywall = Easywall(self.cfg) self.event_handler = ModifiedHandler(self.apply) self.observer = Observer() self.stop_flag = False info("easywall has been started")
def blacklist_save() -> str: """Save the blacklist rules into the corresponding rulesfile.""" utils = Webutils() rules = RulesHandler() if utils.check_login(request) is True: ipaddress = "" rulelist = rules.get_rules_for_web("blacklist") for key, value in request.form.items(): if key == "ipadr": # then a new ip address is blacklisted ipaddress = value rulelist.append(ipaddress) rules.save_new_rules("blacklist", rulelist) else: # then a old ip address is removed ipaddress = key rulelist.remove(ipaddress) rules.save_new_rules("blacklist", rulelist) return blacklist(True) return login()
def whitelist_save(): """ the function saves the whitelist rules into the corresponding rulesfile """ utils = Webutils() rules = RulesHandler() if utils.check_login() is True: ipaddress = "" rulelist = rules.get_rules_for_web("whitelist") for key, value in request.form.items(): if key == "ipadr": # then a new ip address is whitelisted ipaddress = value rulelist.append(ipaddress) rules.save_new_rules("whitelist", rulelist) else: # then a old ip address is removed ipaddress = key rulelist.remove(ipaddress) rules.save_new_rules("whitelist", rulelist) return whitelist(True) return login("", None)
class TestRulesHandler(unittest.TestCase): """ TODO: Doku """ def setUp(self): self.rules = RulesHandler() self.rules.rules_firstrun() def test_firstrun(self): """ TODO: Doku """ self.rules.rules_firstrun() def test_get_current_rules(self): """ TODO: Doku """ write_into_file("{}/current/tcp".format(self.rules.rulesfolder), """80 443 """) self.assertEqual(self.rules.get_current_rules("tcp"), ["80", "443"]) def test_get_new_rules(self): """ TODO: Doku """ write_into_file("{}/new/tcp".format(self.rules.rulesfolder), """80 443 """) self.assertEqual(self.rules.get_new_rules("tcp"), ["80", "443"]) def test_backup_current_rules(self): """ TODO: Doku """ write_into_file("{}/current/tcp".format(self.rules.rulesfolder), """80 443 """) write_into_file("{}/backup/tcp".format(self.rules.rulesfolder), "") self.rules.backup_current_rules() self.assertEqual( file_get_contents("{}/backup/tcp".format(self.rules.rulesfolder)), """80 443 """) def test_apply_new_rules(self): """ TODO: Doku """ write_into_file("{}/new/tcp".format(self.rules.rulesfolder), """80 443 """) write_into_file("{}/current/tcp".format(self.rules.rulesfolder), "") self.assertEqual(self.rules.get_current_rules("tcp"), []) self.rules.apply_new_rules() self.assertEqual(self.rules.get_current_rules("tcp"), ["80", "443"]) def test_rollback_from_backup(self): """ TODO: Doku """ write_into_file("{}/backup/tcp".format(self.rules.rulesfolder), """80 443 """) write_into_file("{}/current/tcp".format(self.rules.rulesfolder), "") self.assertEqual(self.rules.get_current_rules("tcp"), []) self.rules.rollback_from_backup() self.assertEqual(self.rules.get_current_rules("tcp"), ["80", "443"]) def test_get_rules_for_web(self): """ TODO: Doku """ write_into_file("{}/current/tcp".format(self.rules.rulesfolder), """80 443 """) self.assertEqual(self.rules.get_rules_for_web("tcp"), ["80", "443"]) write_into_file("{}/new/tcp".format(self.rules.rulesfolder), """80 443 8080 """) self.assertEqual(self.rules.get_rules_for_web("tcp"), ["80", "443", "8080"]) def test_save_new_rules(self): """ TODO: Doku """ self.rules.save_new_rules("tcp", ["80", "443"]) self.assertEqual( file_get_contents("{}/new/tcp".format(self.rules.rulesfolder)), "80\n443")
class Easywall(): """ the class contains the main functions for the easywall core such as applying a new configuration or listening on rule file changes """ def __init__(self, config: Config) -> None: """TODO: Doku.""" self.cfg = config self.iptables = Iptables(self.cfg) self.acceptance = Acceptance(self.cfg) self.ipv6 = self.cfg.get_value("IPV6", "enabled") self.filepath = "" self.filename = "" self.date = "" self.rules = RulesHandler() def apply(self) -> None: """TODO: Doku.""" self.acceptance.start() self.rotate_backup() self.iptables.save() self.rules.backup_current_rules() self.rules.apply_new_rules() self.apply_iptables() self.acceptance.wait() if self.acceptance.status() == "not accepted": self.iptables.restore() self.rules.rollback_from_backup() info("Configuration was not accepted, rollback applied") else: info("New configuration was applied.") def apply_iptables(self) -> None: """TODO: Doku.""" # and reset iptables for clean setup self.iptables.reset() # drop intbound traffic and allow outbound traffic self.iptables.add_policy(Chain.INPUT, Target.DROP) self.iptables.add_policy(Chain.OUTPUT, Target.ACCEPT) # accept traffic from loopback interface (localhost) self.iptables.add_append(Chain.INPUT, "-i lo -j ACCEPT") # accept established or related connections self.iptables.add_append( Chain.INPUT, "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT") # Block remote packets claiming to be from a loopback address. self.iptables.add_append(Chain.INPUT, "-s 127.0.0.0/8 ! -i lo -j DROP", False, True) self.iptables.add_append(Chain.INPUT, "-s ::1/128 ! -i lo -j DROP", True) # Apply ICMP Rules self.apply_icmp() # forewarded ports self.apply_forwarding() # SSH Brute Force Prevention self.apply_ssh_brute() # ICMP Flood Prevention self.apply_icmp_flood() # drop invalid packets self.apply_invalid_packets_drop() # prevent port scans self.apply_port_scan_prevention() # Apply Broadcast, Multicast and Anycast Rules self.apply_cast() # Block IP-addresses from blacklist self.apply_blacklist() # accept IP-addresses from whitelist self.apply_whitelist() # accept TCP Ports self.apply_rules("tcp") # accept UDP Ports self.apply_rules("udp") # Apply Custom Rules self.apply_custom_rules() # log all dropped connections when enabled if self.cfg.get_value("IPTABLES", "log_blocked_connections"): self.iptables.add_append( Chain.INPUT, "-m limit --limit {}/minute -j LOG --log-prefix \"easywall blocked: \"" .format( self.cfg.get_value("IPTABLES", "log_blocked_connections_log_limit"))) # reject all packages which not match the rules self.iptables.add_append(Chain.INPUT, "-j DROP") def apply_forwarding(self) -> None: """TODO: Doku.""" for ipaddr in self.rules.get_current_rules("forwarding"): proto = ipaddr.split(":")[0] source = ipaddr.split(":")[1] dest = ipaddr.split(":")[2] self.iptables.insert( table="nat", chain=Chain.PREROUTING, rule="-p {} --dport {} -j REDIRECT --to-port {}".format( proto, dest, source)) self.iptables.insert( table="nat", chain=Chain.OUTPUT, rule="-p {} -o lo --dport {} -j REDIRECT --to-port {}".format( proto, dest, source)) self.iptables.add_append( chain=Chain.INPUT, rule="-p {} --dport {} -m conntrack --ctstate NEW -j ACCEPT". format(proto, source)) self.iptables.add_append( chain=Chain.INPUT, rule="-p {} --dport {} -m conntrack --ctstate NEW -j ACCEPT". format(proto, dest)) def apply_ssh_brute(self) -> None: """TODO: Doku.""" if self.cfg.get_value("IPTABLES", "ssh_brute_force_prevention"): connection_limit = self.cfg.get_value( "IPTABLES", "ssh_brute_force_prevention_connection_limit") log_enable = self.cfg.get_value("IPTABLES", "ssh_brute_force_prevention_log") log_limit = self.cfg.get_value( "IPTABLES", "ssh_brute_force_prevention_log_limit") log_prefix = "easywall ssh-brute blocked: " self.iptables.add_chain("SSHBRUTE") self.iptables.add_append(Chain.SSHBRUTE, "-m recent --name SSH --set") if log_enable: self.iptables.add_append( Chain.SSHBRUTE, "-m recent --name SSH --update --seconds 60 --hitcount " + "{} -m limit --limit {}/minute -j LOG --log-prefix \"{}\"". format(connection_limit, log_limit, log_prefix)) self.iptables.add_append( Chain.SSHBRUTE, "-m recent --name SSH --update --seconds 60 --hitcount {} -j DROP" .format(connection_limit)) self.iptables.add_append(Chain.SSHBRUTE, "-j ACCEPT") def apply_invalid_packets_drop(self) -> None: """TODO: Doku.""" if self.cfg.get_value("IPTABLES", "drop_invalid_packets"): log_enable = self.cfg.get_value("IPTABLES", "drop_invalid_packets_log") log_limit = self.cfg.get_value("IPTABLES", "drop_invalid_packets_log_limit") log_prefix = "easywall invalid packet blocked: " self.iptables.add_chain("INVALIDDROP") if log_enable: self.iptables.add_append( Chain.INVALIDDROP, "-m state --state INVALID -m limit --limit {}/m -j LOG --log-prefix \"{}\"" .format(log_limit, log_prefix)) self.iptables.add_append(Chain.INVALIDDROP, "-m state --state INVALID -j DROP") self.iptables.add_append( Chain.INPUT, "-m state --state INVALID -j INVALIDDROP", ) def apply_port_scan_prevention(self) -> None: """TODO: Doku.""" if self.cfg.get_value("IPTABLES", "port_scan_prevention"): log_enable = self.cfg.get_value("IPTABLES", "port_scan_prevention_log") log_limit = self.cfg.get_value("IPTABLES", "port_scan_prevention_log_limit") log_prefix = "easywall port scan blocked: " self.iptables.add_chain("PORTSCAN") if log_enable: self.iptables.add_append( Chain.PORTSCAN, "-m limit --limit {}/m -j LOG --log-prefix \"{}\"".format( log_limit, log_prefix)) self.iptables.add_append(Chain.PORTSCAN, "-j DROP") # nmap Null scans / no flags self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags ALL NONE -j PORTSCAN") # nmap FIN stealth scan self.iptables.add_append(Chain.INPUT, "-p tcp --tcp-flags ALL FIN -j PORTSCAN") # SYN + FIN self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags SYN,FIN SYN,FIN -j PORTSCAN") # SYN + RST self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags SYN,RST SYN,RST -j PORTSCAN") # FIN + RST self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags FIN,RST FIN,RST -j PORTSCAN") # FIN + URG + PSH self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags ALL FIN,URG,PSH -j PORTSCAN") # XMAS self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags ALL URG,ACK,PSH,RST,SYN,FIN -j PORTSCAN") # ALL self.iptables.add_append(Chain.INPUT, "-p tcp --tcp-flags ALL ALL -j PORTSCAN") # FIN/PSH/URG without ACK self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags ACK,FIN FIN -j PORTSCAN") self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags ACK,PSH PSH -j PORTSCAN") self.iptables.add_append( Chain.INPUT, "-p tcp --tcp-flags ACK,URG URG -j PORTSCAN") def apply_icmp_flood(self) -> None: """TODO: Doku.""" if self.cfg.get_value("IPTABLES", "icmp_flood_prevention"): connection_limit = self.cfg.get_value( "IPTABLES", "icmp_flood_prevention_connection_limit") log_enable = self.cfg.get_value("IPTABLES", "icmp_flood_prevention_log") log_limit = self.cfg.get_value("IPTABLES", "icmp_flood_prevention_log_limit") log_prefix = "easywall icmp-flood blocked: " self.iptables.add_chain("ICMPFLOOD") self.iptables.add_append(Chain.ICMPFLOOD, "-m recent --set --name ICMP --rsource") if log_enable: self.iptables.add_append( Chain.ICMPFLOOD, "-m recent --update --seconds 1 --hitcount " + "{} --name ICMP --rsource --rttl -m limit ".format( connection_limit) + "--limit {}/minute -j LOG --log-prefix \"{}\"".format( log_limit, log_prefix)) self.iptables.add_append( Chain.ICMPFLOOD, "-m recent --update --seconds 1 --hitcount {} --name ICMP --rsource --rttl -j DROP" .format(connection_limit)) self.iptables.add_append(Chain.ICMPFLOOD, "-j ACCEPT") self.iptables.add_append( Chain.INPUT, "-p icmp --icmp-type 8 -m conntrack --ctstate NEW -j ICMPFLOOD", onlyv4=True) if self.ipv6: self.iptables.add_append( Chain.INPUT, "-p ipv6-icmp --icmpv6-type 128 -j ICMPFLOOD", onlyv6=True) def apply_icmp(self) -> None: """ this function adds rules to iptables for incoming ICMP requests """ icmpv4types = [0, 3, 11, 12] # 0 = echo-reply # 3 = destination-unreachable # 11 = time-exceeded # 12 = parameter problem icmpv6types = [1, 2, 3, 4, 128, 129] # 1 = destination-unreachable # 2 = packet-too-big # 3 = time-exceeded # 4 = parameter-problem # 128 = echo-request # 129 = echo-reply if self.cfg.get_value("IPV6", "icmp_allow_router_advertisement"): icmpv6types.append(133) icmpv6types.append(134) # 133 = router solicitation # 134 = router advertisement if self.cfg.get_value("IPV6", "icmp_allow_neighbor_advertisement"): icmpv6types.append(135) icmpv6types.append(136) # 135 = neighbor solicitation # 136 = neighbor advertisement for icmptype in icmpv4types: self.iptables.add_append( Chain.INPUT, "-p icmp --icmp-type {} -m conntrack --ctstate NEW -j ACCEPT". format(icmptype), False, True) if self.ipv6 is True: for icmptype in icmpv6types: self.iptables.add_append( Chain.INPUT, "-p ipv6-icmp --icmpv6-type {} -j ACCEPT".format(icmptype), True) def apply_cast(self) -> None: """TODO: Doku.""" if self.cfg.get_value("IPTABLES", "drop_broadcast_packets"): self.iptables.add_append( Chain.INPUT, "-m addrtype --dst-type BROADCAST -j DROP", onlyv4=True) if self.cfg.get_value("IPTABLES", "drop_multicast_packets"): self.iptables.add_append( Chain.INPUT, "-m addrtype --dst-type MULTICAST -j DROP", onlyv4=True) self.iptables.add_append(Chain.INPUT, "-d 224.0.0.0/4 -j DROP", onlyv4=True) if self.ipv6 is True: self.iptables.add_append( Chain.INPUT, "-m addrtype --dst-type MULTICAST -j DROP", onlyv6=True) if self.cfg.get_value("IPTABLES", "drop_anycast_packets"): self.iptables.add_append(Chain.INPUT, "-m addrtype --dst-type ANYCAST -j DROP", onlyv4=True) if self.ipv6 is True: self.iptables.add_append( Chain.INPUT, "-m addrtype --dst-type ANYCAST -j DROP", onlyv6=True) def apply_blacklist(self) -> None: """ this function adds rules to iptables which block incoming traffic from a list of ip addresses """ for ipaddr in self.rules.get_current_rules("blacklist"): log_enable = self.cfg.get_value("IPTABLES", "log_blacklist_connections") log_limit = self.cfg.get_value( "IPTABLES", "log_blacklist_connections_log_limit") log_prefix = "easywall blacklist blocked: " if ":" in ipaddr: if log_enable: self.iptables.add_append( chain=Chain.INPUT, rule= "-s {} -m limit --limit {}/m -j LOG --log-prefix \"{}\"" .format(ipaddr, log_limit, log_prefix), onlyv6=True) self.iptables.add_append(Chain.INPUT, "-s {} -j DROP".format(ipaddr), onlyv6=True) else: if log_enable: self.iptables.add_append( chain=Chain.INPUT, rule= "-s {} -m limit --limit {}/m -j LOG --log-prefix \"{}\"" .format(ipaddr, log_limit, log_prefix), onlyv4=True) self.iptables.add_append(Chain.INPUT, "-s {} -j DROP".format(ipaddr), onlyv4=True) def apply_whitelist(self) -> None: """ this function adds rules to iptables which explicitly accepts a connection from this list ip addresses """ for ipaddr in self.rules.get_current_rules("whitelist"): if ":" in ipaddr: self.iptables.add_append(Chain.INPUT, "-s {} -j ACCEPT".format(ipaddr), onlyv6=True) else: self.iptables.add_append(Chain.INPUT, "-s {} -j ACCEPT".format(ipaddr), onlyv4=True) def apply_rules(self, ruletype: str) -> None: """ this function adds rules for incoming tcp and udp connections to iptables which accept a connection to this list of ports [INFO] the function also processes port ranges split by ":" separator. """ for port in self.rules.get_current_rules(ruletype): jail = "ACCEPT" if port["ssh"]: jail = "SSHBRUTE" if ":" in port["port"]: rule = "-p {} --match multiport --dports {}".format( ruletype, port["port"]) else: rule = "-p {} --dport {}".format(ruletype, port["port"]) self.iptables.add_append( chain=Chain.INPUT, rule="{} -m conntrack --ctstate NEW -j {}".format(rule, jail)) def apply_custom_rules(self) -> None: """TODO: Doku.""" for rule in self.rules.get_current_rules("custom"): if rule != "": if not rule.startswith("#"): self.iptables.add_custom(rule=rule) def rotate_backup(self) -> None: """TODO: Doku.""" self.filepath = "backup" self.filename = "iptables_v4_backup" self.date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") self.rename_backup_file() if self.ipv6 is True: self.filename = "iptables_v6_backup" self.rename_backup_file() debug("backup file rotated in folder {} \n prefix added: {}".format( self.filepath, self.date)) def rename_backup_file(self) -> None: """TODO: Doku.""" old_filename = "{}/{}".format(self.filepath, self.filename) new_filename = "{}/{}_{}".format(self.filepath, self.date, self.filename) if file_exists(old_filename): rename_file(old_filename, new_filename)
def setUp(self) -> None: """TODO: Doku.""" self.rules = RulesHandler()
class TestRulesHandler(unittest.TestCase): """TODO: Doku.""" def setUp(self) -> None: """TODO: Doku.""" self.rules = RulesHandler() def test_get_current_rules(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.rules.apply_new_rules() self.assertEqual(self.rules.get_current_rules("tcp"), ports) def test_get_new_rules(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.assertEqual(self.rules.get_new_rules("tcp"), ports) def test_backup_current_rules(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.rules.apply_new_rules() self.rules.backup_current_rules() self.assertEqual(self.rules.get_backup_rules("tcp"), ports) def test_apply_new_rules(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", []) self.rules.apply_new_rules() self.assertEqual(self.rules.get_current_rules("tcp"), []) self.rules.save_new_rules("tcp", ports) self.rules.apply_new_rules() self.assertEqual(self.rules.get_current_rules("tcp"), ports) def test_get_rules_for_web(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.rules.apply_new_rules() self.assertEqual(self.rules.get_rules_for_web("tcp"), ports) ports = [] entry = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "8080" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.assertEqual(self.rules.get_rules_for_web("tcp"), ports) def test_rollback_from_backup(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.rules.apply_new_rules() self.rules.backup_current_rules() self.rules.save_new_rules("tcp", []) self.rules.apply_new_rules() self.assertEqual(self.rules.get_current_rules("tcp"), []) self.rules.rollback_from_backup() self.assertEqual(self.rules.get_current_rules("tcp"), ports) def test_diff_new_current(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "123" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "1234" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.rules.apply_new_rules() ports = [] entry = {} entry["description"] = "test" entry["port"] = "1337" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.assertTrue(self.rules.diff_new_current("tcp")) self.rules.apply_new_rules() self.assertFalse(self.rules.diff_new_current("tcp")) def test_save_new_rules(self) -> None: """TODO: Doku.""" ports: list = [] entry: dict = {} entry["description"] = "test" entry["port"] = "80" entry["ssh"] = False ports.append(entry) entry = {} entry["description"] = "test" entry["port"] = "443" entry["ssh"] = False ports.append(entry) self.rules.save_new_rules("tcp", ports) self.assertEqual(self.rules.get_new_rules("tcp"), ports)
class Easywall(object): """ the class contains the main functions for the easywall core such as applying a new configuration or listening on rule file changes """ def __init__(self, config: Config) -> None: self.cfg = config self.iptables = Iptables(self.cfg) self.acceptance = Acceptance(self.cfg) self.ipv6 = self.cfg.get_value("IPV6", "enabled") self.filepath = None self.filename = None self.date = None self.rules = RulesHandler() def apply(self) -> None: """ TODO: Doku """ self.acceptance.start() self.rotate_backup() self.iptables.save() self.rules.backup_current_rules() self.rules.apply_new_rules() self.apply_iptables() self.acceptance.wait() if self.acceptance.status() == "not accepted": self.iptables.restore() self.rules.rollback_from_backup() info("Configuration was not accepted, rollback applied") else: info("New configuration was applied.") def apply_iptables(self) -> None: """ TODO: Doku """ # and reset iptables for clean setup self.iptables.reset() # drop intbound traffic and allow outbound traffic self.iptables.add_policy("INPUT", "DROP") self.iptables.add_policy("OUTPUT", "ACCEPT") # allow loopback access self.iptables.add_append("INPUT", "-i lo -j ACCEPT") # allow established or related connections self.iptables.add_append( "INPUT", "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT") # Block remote packets claiming to be from a loopback address. self.iptables.add_append("INPUT", "-s 127.0.0.0/8 ! -i lo -j DROP", False, True) self.iptables.add_append("INPUT", "-s ::1/128 ! -i lo -j DROP", True) # Apply ICMP Rules self.apply_icmp() # Block IP-addresses from blacklist self.apply_blacklist() # Allow IP-addresses from whitelist self.apply_whitelist() # Allow TCP Ports self.apply_rules("tcp") # Allow UDP Ports self.apply_rules("udp") # Apply Custom Rules self.apply_custom_rules() # log and reject all other packages self.iptables.add_append("INPUT", "-j LOG --log-prefix \" easywall[other]: \"") self.iptables.add_append("INPUT", "-j REJECT") def apply_icmp(self) -> None: """ this function adds rules to iptables for incoming ICMP requests """ for icmptype in [0, 3, 8, 11]: self.iptables.add_append( "INPUT", "-p icmp --icmp-type {} -m conntrack --ctstate NEW -j ACCEPT". format(icmptype), False, True) if self.ipv6 is True: for icmptype in [ 1, 2, 3, 4, 128, 133, 134, 135, 136, 137, 141, 142, 151, 152, 153 ]: self.iptables.add_append( "INPUT", "-p ipv6-icmp --icmpv6-type {} -j ACCEPT".format(icmptype), True) def apply_blacklist(self) -> None: """ this function adds rules to iptables which block incoming traffic from a list of ip addresses """ for ipaddr in self.rules.get_current_rules("blacklist"): if ":" in ipaddr: self.iptables.add_append( chain="INPUT", rule="-s {} -j LOG --log-prefix \" easywall[blacklist]: \"" .format(ipaddr), onlyv6=True) self.iptables.add_append("INPUT", "-s {} -j DROP".format(ipaddr), onlyv6=True) else: self.iptables.add_append( chain="INPUT", rule="-s {} -j LOG --log-prefix \" easywall[blacklist]: \"" .format(ipaddr), onlyv4=True) self.iptables.add_append("INPUT", "-s {} -j DROP".format(ipaddr), onlyv4=True) def apply_whitelist(self) -> None: """ this function adds rules to iptables which explicitly allows a connection from this list ip addresses """ for ipaddr in self.rules.get_current_rules("whitelist"): if ":" in ipaddr: self.iptables.add_append("INPUT", "-s {} -j ACCEPT".format(ipaddr), onlyv6=True) else: self.iptables.add_append("INPUT", "-s {} -j ACCEPT".format(ipaddr), onlyv4=True) def apply_rules(self, ruletype) -> None: """ this function adds rules for incoming tcp and udp connections to iptables which allow a connection to this list of ports [INFO] the function also processes port ranges split by ":" separator. """ for port in self.rules.get_current_rules(ruletype): if ":" in port: rule = "-p {} --match multiport --dports {}".format( ruletype, port) else: rule = "-p {} --dport {}".format(ruletype, port) self.iptables.add_append( chain="INPUT", rule="{} -m conntrack --ctstate NEW -j ACCEPT".format(rule)) def apply_custom_rules(self) -> None: """ TODO: Doku """ for rule in self.rules.get_current_rules("custom"): self.iptables.add_append(chain="INPUT", rule=rule) def rotate_backup(self) -> None: """ TODO: Doku """ self.filepath = "backup" self.filename = "iptables_v4_backup" self.date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") self.rename_backup_file() if self.ipv6 is True: self.filename = "iptables_v6_backup" self.rename_backup_file() debug("backup file rotated in folder {} \n prefix added: {}".format( self.filepath, self.date)) def rename_backup_file(self) -> None: """ TODO: Doku """ old_filename = "{}/{}".format(self.filepath, self.filename) new_filename = "{}/{}_{}".format(self.filepath, self.date, self.filename) if file_exists(old_filename): rename_file(old_filename, new_filename)
class Main(object): """ TODO: Doku """ def __init__(self): self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(loglevel, to_stdout, to_files, logpath, logfile) info("starting easywall") self.is_first_run = not folder_exists("rules") self.rules_handler = RulesHandler() if self.is_first_run: self.rules_handler.rules_firstrun() self.easywall = Easywall(self.cfg) self.event_handler = ModifiedHandler(self.apply) self.observer = Observer() self.stop_flag = False info("easywall has been started") def apply(self, filename: str) -> None: """ TODO: Doku """ info("starting apply process from easywall") delete_file_if_exists(filename) self.easywall.apply() def start_observer(self) -> None: """ this function is called to keep the main process running if someone is pressing ctrl + C the software will initiate the stop process """ self.observer.schedule(self.event_handler, ".") self.observer.start() try: while not self.stop_flag: sleep(2) except KeyboardInterrupt: info("KeyboardInterrupt received, starting shutdown") finally: self.shutdown() def shutdown(self) -> None: """ the function stops all threads and shuts the software down gracefully """ info("starting shutdown") self.observer.stop() delete_file_if_exists(".acceptance") self.observer.join() info("shutdown completed") self.log.close_logging()
def remove_port(port, ruletype): """the function removes a port from the opened port rules file""" rules = RulesHandler() rulelist = rules.get_rules_for_web(ruletype) rulelist.remove(port) rules.save_new_rules(ruletype, rulelist)
def add_port(port, ruletype): """the function adds a port to the opened port rules file""" rules = RulesHandler() rulelist = rules.get_rules_for_web(ruletype) rulelist.append(port) rules.save_new_rules(ruletype, rulelist)
def setUp(self): self.rules = RulesHandler() self.rules.rules_firstrun()