コード例 #1
0
ファイル: libjunxon.py プロジェクト: mankuthimma/junXon
 def check_subscription(self, ipaddress):
     table = Table("nat")
     rules = table.list_rules("POSTROUTING")
     for r in rules:
         if r.source == ipaddress:
             return True
     return False
コード例 #2
0
class PortKnocker():
    '''
    Class for analysis iptables log messages and open port upon attempt connect to port.
    '''
    def __init__(self, *args, **kwargs):
        # Files for processing.
        self.pidfile = kwargs["pidfile"]
        self.portknocker_logfile = "/var/log/portknocker_daemon.log"
        self.iptables_logfile = "/dev/shm/iptables.log"
        self.dumpfile = os.path.dirname(
            os.path.realpath(__file__)) + os.sep + "portknocker.dmp"

        # Logger parameters for daemon.
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.INFO)
        self.fh = logging.FileHandler(self.portknocker_logfile)
        self.fh.setLevel(logging.INFO)
        self.formatstr = '%(asctime)s - %(levelname)s - %(message)s'
        self.formatter = logging.Formatter(self.formatstr)
        self.fh.setFormatter(self.formatter)
        self.logger.addHandler(self.fh)

        # Netfilter table for setting rules.
        self.table = Table("filter")

        # Reserved ports {"in_int:port":"src_ip"}.
        self.ports = dict()

        # Clean for start, stop and restart daemon.
        self.clean()

    def tail(self, monfile):
        '''
        Get last line in file,  similarly to tail -f filename.
        '''
        try:
            while True:
                for line in tailer.follow(monfile, 1):
                    time.sleep(0.1)
                    yield line
        except:
            self.logger.error("Error while log-file parsing.")

    def set_rule(self, in_int, port, src_ip):
        '''
        Make netfilter rule.
        -I INPUT -i $int_in -p tcp --dport $port -j ACCEPT
        '''
        rule = Rule(in_interface=f"{in_int}",
                    source=f"{src_ip}",
                    protocol="tcp",
                    matches=[Match("tcp", f"--dport {port}")],
                    jump="ACCEPT")
        return rule

    def check_netfilter_rule(self, in_int, port, src_ip):
        '''
        Check rule availability in netfilter.
        '''
        return True if self.set_rule(in_int, port, src_ip) in [
            _rule for _rule in self.table.list_rules("INPUT")
        ] else False

    def set_netfilter_rule(self, in_int, port, src_ip):
        '''
        Set netfilter rule.
        '''
        try:
            if not self.check_netfilter_rule(in_int, port, src_ip):
                self.table.prepend_rule("INPUT",
                                        self.set_rule(in_int, port, src_ip))
                self.logger.info(
                    f"Add netfilter rule for in_int:{in_int} port:{port} src_ip:{src_ip}"
                )
        except:
            self.logger.error(
                f"Rule setting error, in_int:{in_int} port:{port} src_ip:{src_ip}"
            )

    def delete_netfilter_rule(self, in_int, port, src_ip):
        '''
        Delete netfilter rule.
        -D INPUT -t in_int -s src_ip --dport $port
        '''
        try:
            if self.check_netfilter_rule(in_int, port, src_ip):
                self.table.delete_rule('INPUT',
                                       self.set_rule(in_int, port, src_ip))
                self.logger.info(
                    f"Delete netfilter rule for in_int:{in_int} port:{port} src_ip:{src_ip}"
                )
        except:
            self.logger.error(
                f"Error removing netfilter rule, in_int:{in_int} port:{port} src_ip:{src_ip}"
            )

    def checking_established_connections(self, in_int, port, src_ip):
        '''
        Check established port connection.
        If not established connection, then delete for opening port netfilter rule.
        '''
        while True:
            # Timeoute for establish connection.
            time.sleep(30)
            # Enumeration of available connections by port number and connection status.
            if [port, "ESTABLISHED"] in [[str(conn.laddr.port), conn.status]
                                         for conn in psutil.net_connections()]:
                self.logger.info(
                    f"Connection ESTABLISHED port:{port} src_ip:{src_ip}")
                # Timeout before next check connection status.
                time.sleep(30)
            else:
                try:
                    # Delete iptables rule for port opening..
                    self.delete_netfilter_rule(in_int, port, src_ip)
                    self.logger.info(
                        f"Disconnect in_int: port:{port} src_ip:{src_ip}")
                    # Delete port from list reservated.
                    self.deleting_reserved_port(f"{in_int}:{port}")
                    return True
                except:
                    self.logger.error(
                        f"Error while closing the connection, port:{port} src_ip:{src_ip}"
                    )
                    return False

    def save_reservated_ports_set_to_disk(self):
        '''
        Save list reserved ports to file.
        '''
        try:
            with open(self.dumpfile, "w") as dumpfile:
                if len(self.ports):
                    for in_int_port, src_ip in self.ports.items():
                        dumpfile.write(f"{in_int_port}:{src_ip}\n")
            os.chmod(self.dumpfile, 0o600)
        except:
            self.logger.error("Error writing dump file.")

    def get_reservated_ports_from_dumpfile(self):
        '''
        Get list reserved ports from file.
        '''
        try:
            if os.path.exists(
                    self.dumpfile) and (os.path.getsize(self.dumpfile) > 1):
                with open(self.dumpfile, "r") as dumpfile:
                    self.ports = {
                        f"{in_int}:{port}": src_ip
                        for in_int, port, src_ip in [
                            port.rstrip().split(':')
                            for port in dumpfile.readlines()
                        ]
                    }
        except:
            self.logger.error("Error while get reserved ports from file.")

    def set_reservated_port(self, in_int, port, src_ip):
        '''
        Save port number to list reserved ports and save this list to file.
        '''
        try:
            self.ports[f"{in_int}:{port}"] = src_ip
            self.save_reservated_ports_set_to_disk()
        except:
            self.logger.error("Error saving the reserved port.")

    def deleting_all_created_rules(self):
        '''
        Delete all set rule.
        If in memory not reserved ports, then checking records in dumpfile.
        '''
        try:
            # Actions "start" and "stop" create new class copy,
            # him need loading list reserved ports.в
            if not len(self.ports):
                self.get_reservated_ports_from_dumpfile()
            for in_int_port, src_ip in self.ports.items():
                in_int, port = in_int_port.split(':')
                self.delete_netfilter_rule(in_int, port, src_ip)
        except:
            self.logger.warning(
                "Error while remove all rule with reserved ports.")
            return False
        return True

    def clean_reservated_port_file(self):
        '''
        Delete dumpfile.
        '''
        try:
            os.remove(self.dumpfile)
        except:
            self.logger.warning(
                "Error while remove dumpfile. Dumpfile may be missing.")

    def deleting_reserved_port(self, in_int_port):
        '''
        Delete port from list reserved ports and
        save actual list to disk.
        '''
        try:
            if len(self.ports):
                self.ports.pop(in_int_port, 0)
                self.save_reservated_ports_set_to_disk()
        except:
            self.logger.error("Error deleting reserved port.")

    def port_connection_reservate(self):
        '''
        Reserve port and set netfilter rule for port opening.
        '''
        # Counter attempting connection to port.
        port_knock_counter = {}
        # Dictionary of active timer threads.
        timer_threads = {}
        # Dictionary of threads active observed connections.
        connection_threads = {}

        def reservate(in_int, port, src_ip):
            # Port connection attempt counting.
            port_knock_counter[port] = port_knock_counter.setdefault(port, 0)
            port_knock_counter[port] += 1
            # If attempt not one, then running timer.
            if port_knock_counter[port] == 2:
                timer_thread = Thread(name=port,
                                      target=time.sleep,
                                      args=(10, ),
                                      daemon=True)
                timer_threads[port] = timer_thread
                timer_thread.start()
            # Reservate port after five attempting and reset attempt counter.
            if port_knock_counter[port] == 5:
                self.set_reservated_port(in_int, port, src_ip)
                port_knock_counter[port] = 0
                # Because checking thread activity can call exception,  do this.
                connection_threads[port] = Thread(name=port)
                if not timer_threads[port].is_alive(
                ) and not connection_threads[port].is_alive():
                    # Reset attempt counter after stopping timer.
                    port_knock_counter[port] = 0
                # Create netfilter rule for reserved port and starting observed.
                if [
                        key for key in self.ports.keys()
                        if [in_int, port] in [key.split(':')]
                ]:
                    # Sets netfilter rules.
                    self.set_netfilter_rule(in_int, port, src_ip)
                    try:
                        # The flow, in which carried out observant to connection and deleting netfilter rule after disconnect to port.
                        connection_thread = Thread(
                            name=port,
                            target=self.checking_established_connections,
                            args=(
                                in_int,
                                port,
                                src_ip,
                            ),
                            daemon=True)
                        connection_threads[port] = connection_thread
                        connection_thread.start()
                    except:
                        self.logger.error(
                            'Error creating connection control thread!')

        return reservate

    def clean(self):
        '''
        Netfilter rules and dumpfile clean when daemon restarted or stopping.
        '''
        try:
            self.deleting_all_created_rules()
            self.clean_reservated_port_file()
            self.logger.info('PortKnocker stopped ...')
        except:
            self.logger.error('Error clearing rules.')

    def run(self):
        '''
        Master cycle running netfilter log file analyser for search in him new connection records. 
        You need set netfilter rule, equivalent to this template:
        start_port=10000:10049,10051 && \
        end_port=50000 && \
        iptables -I INPUT -i enp0s3 -p tcp -m multiport --dports $start_port:$end_port -j DROP && \
        iptables -I INPUT -i enp0s3 -p tcp -m multiport --dports $start_port:$end_port -j LOG --log-prefix "Iptables: port_knocker " 
        '''
        self.setting_up_rsyslog()
        self.logger.info("PortKnocker running ...")
        reserved_port = self.port_connection_reservate()
        with open(self.iptables_logfile, "w+") as f:
            for line in self.tail(f):
                # Long enough record in netfilter log file containing: string "Iptables: port_knocker",
                # input interface, destination port and note about SYN type package.
                if ("Iptables: port_knocker"
                        in line) and (line.find(" DPT=")
                                      and line.find(" SYN ")):
                    reserved_port(
                        str(*re.findall(r"IN=\w+", line))[3:],
                        str(*re.findall(r"DPT=\d+", line))[4:],
                        str(*re.findall(r"SRC=[\d+\.]+", line))[4:])
                self.clean_log()

    def setting_up_rsyslog(self):
        '''
        Configuring rsislog settings for iptables logging.
        '''
        rsyslog_confd_file = "/etc/rsyslog.d/10-iptables.conf"
        rsyslog_cfg_string = f':msg, contains, "Iptables: port_knocker " -{self.iptables_logfile}\n& ~'
        try:
            with open(rsyslog_confd_file, "r") as rsys_confd:
                for line in rsys_confd.readlines():
                    if "Iptables: port_knocker" in line:
                        return
                raise FileNotFoundError
        except FileNotFoundError:
            with open(rsyslog_confd_file, "w+") as rsys_confd:
                rsys_confd.write(rsyslog_cfg_string)
                self.logger.info(f"Added settings to {rsyslog_confd_file}")
        try:
            os.system("systemctl restart rsyslog")
        except:
            self.logger.error(
                "Check for the rsyslog service. Portknocker demon stopped.")
            self.clean()
            sys.exit()

    def clean_log(self, *args, **kwargs):
        '''
        Cleaning log-files.
        '''
        try:
            logfile = kwargs["logfile"]
            critical_size = kwargs["critical_size"]
        except:
            logfile = self.iptables_logfile
            critical_size = 1048576
        try:
            if os.path.getsize(logfile) >= critical_size:
                open(logfile, "w").close()
        except:
            self.logger.warning(f"Failed to clean up {logfile} file")

    def _demonize(self):
        '''
        Demonization of the portknocker process.
        '''
        self._terminate_daemon_process()
        if os.path.exists(self.pidfile):
            try:
                os.remove(self.pidfile)
            except:
                self.logger.error(
                    f"Pleas check {self.pidfile} permissons and delete him.")
                sys.exit()
        self._start()

    def _start(self):
        '''
        Set the daemon context and run the application.
        '''
        pidlock = PIDLockFile(self.pidfile)
        with DaemonContext(
                pidfile=pidlock,
                working_directory=os.path.dirname(os.path.realpath(__file__)) +
                os.sep,
                stdout=self.fh.stream,
                stderr=self.fh.stream):
            return self.run()

    def _terminate_daemon_process(self):
        '''
        Exit the daemon process specified in the current PID file.
        '''
        self.clean()
        try:
            with open(self.pidfile, "r") as pidfile:
                pid = int(pidfile.readline().strip())
        except:
            self.logger.error(f"Failed to read pidfile {self.pidfile}")
            return
        try:
            os.kill(pid, signal.SIGTERM)
            self.logger.info("Portknocker demon stopped.")
        except:
            self.logger.error(f"Failed to terminate {pid}.")
            return

    def _restart(self):
        '''
        Restart daemon.
        '''
        self._terminate_daemon_process()
        self._start()