예제 #1
0
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)
예제 #2
0
    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')
예제 #3
0
    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')
예제 #4
0
파일: ports.py 프로젝트: ptrifonov/easywall
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)
예제 #5
0
파일: ports.py 프로젝트: ptrifonov/easywall
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)
예제 #6
0
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)
예제 #7
0
 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()
예제 #8
0
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
예제 #9
0
파일: custom.py 프로젝트: zroorz/easywall
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()
예제 #10
0
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)
예제 #11
0
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)
예제 #12
0
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
예제 #13
0
    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')
예제 #14
0
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()
예제 #15
0
파일: custom.py 프로젝트: zroorz/easywall
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()
예제 #16
0
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()
예제 #17
0
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()
예제 #18
0
    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")
예제 #19
0
    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")
예제 #20
0
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()
예제 #21
0
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)
예제 #22
0
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")
예제 #23
0
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)
예제 #24
0
 def setUp(self) -> None:
     """TODO: Doku."""
     self.rules = RulesHandler()
예제 #25
0
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)
예제 #26
0
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)
예제 #27
0
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()
예제 #28
0
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)
예제 #29
0
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)
예제 #30
0
 def setUp(self):
     self.rules = RulesHandler()
     self.rules.rules_firstrun()