logparse / parsers / ufw.pyon commit rename parsers, better journald integration (e1f7605)
   1"""
   2Get details about packets blocked by ufw (uses journald)
   3"""
   4
   5import datetime
   6import re
   7from systemd import journal
   8
   9from logparse import config
  10from logparse.formatting import *
  11from logparse.load_parsers import Parser
  12from logparse.util import resolve
  13
  14PROTOCOLS = ["TCP", "UDP", "UDP-Lite", "ICMP", "ICMPv6", "AH", "SCTP", "MH"]
  15
  16class Packet():
  17    """
  18    Class to hold variables for each packet. Also parses incoming log messages
  19    on object initialisation.
  20    """
  21
  22    def __init__(self, msg):
  23        """
  24        Determine fields in log message. If any of the fields are missing the
  25        log is considered malformed and is discarded. Also the protocol can be
  26        specified as either an integer or a string - see `man ufw`.
  27        """
  28        try:
  29            self.inif, self.outif, self.mac, self.src, self.dst, self.len, \
  30                    self.proto, self.spt, self.dpt = \
  31                    re.search(r"IN=(?P<inif>\w*).*OUT=(?P<outif>\w*).*"
  32                        "MAC=(?P<mac>\S*).*SRC=(?P<src>\S*).*DST=(?P<dst>\S*)"
  33                        ".*LEN=(?P<length>\d*).*PROTO=(?P<proto>\S*)"
  34                        "(?:\sSPT=(?P<spt>\d*))?(?:\sDPT=(?P<dpt>\d*))?", msg
  35                    ).groupdict().values()
  36            if self.proto and self.proto.isdigit():
  37                self.proto = PROTOCOLS[int(self.proto)-1]
  38        except Exception as e:
  39            logger.warning("Malformed packet log: {0}. Error message: {1}"
  40                    .format(msg, str(e)))
  41            return None
  42
  43class UfwJournald(Parser):
  44
  45    def __init__(self):
  46        super().__init__()
  47        self.name = "ufw"
  48        self.info = "Get details about packets blocked by ufw"
  49
  50    def parse_log(self):
  51
  52        logger.debug("Starting ufw section")
  53        section = Section("ufw")
  54
  55        # Find applicable log entries
  56
  57        j = journal.Reader()
  58        j.this_machine()
  59        j.add_match(_TRANSPORT='kernel')
  60        j.add_match(PRIORITY=4)
  61        j.seek_realtime(section.period.startdate)
  62        
  63        logger.debug("Searching for messages")
  64
  65        blocked_packets = [Packet(entry["MESSAGE"]) for entry in j
  66                if "MESSAGE" in entry and "UFW BLOCK" in entry["MESSAGE"]]
  67
  68        # Parse messages
  69
  70        logger.debug("Parsing messages")
  71
  72        inbound_interfaces = []
  73        outbound_interfaces = []
  74        n_inbound = n_outbond = 0
  75        src_ips = []
  76        dst_ips = []
  77        src_ports = []
  78        dst_ports = []
  79        protocols = {'UDP': 0, 'TCP': 0}
  80        src_macs = []
  81
  82        for pkt in blocked_packets:
  83            if pkt.inif:
  84                inbound_interfaces.append(pkt.inif)
  85            elif pkt.outif:
  86                outbound_interfaces.append(pkt.outif)
  87            if pkt.src: src_ips.append(resolve(pkt.src,
  88                config.prefs.get("ufw", "ufw-resolve-domains")))
  89            if pkt.dst: dst_ips.append(resolve(pkt.dst,
  90                config.prefs.get("ufw", "ufw-resolve-domains")))
  91            if pkt.spt: src_ports.append(pkt.spt)
  92            if pkt.dpt: dst_ports.append(pkt.dpt)
  93            if pkt.proto: protocols[pkt.proto] += 1
  94
  95        # Format data objects
  96
  97        section.append_data(Data(subtitle="{} blocked ({} UDP, {} TCP)".format(
  98                plural("packet", len(blocked_packets)),
  99                protocols['UDP'], protocols['TCP'])))
 100
 101        src_port_data = Data(items=src_ports)
 102        src_port_data.orderbyfreq()
 103        src_port_data.subtitle = plural("source port", len(src_port_data.items))
 104        src_port_data.truncl(config.prefs.getint("logparse", "maxlist"))
 105        section.append_data(src_port_data)
 106
 107        dst_port_data= Data(items=dst_ports)
 108        dst_port_data.orderbyfreq()
 109        dst_port_data.subtitle = plural("destination port",
 110                len(dst_port_data.items))
 111        dst_port_data.truncl(config.prefs.getint("logparse", "maxlist"))
 112        section.append_data(dst_port_data)
 113
 114        src_ips_data= Data(items=src_ips)
 115        src_ips_data.orderbyfreq()
 116        src_ips_data.subtitle = plural("source IP", len(src_ips_data.items))
 117        src_ips_data.truncl(config.prefs.getint("logparse", "maxlist"))
 118        section.append_data(src_ips_data)
 119
 120        dst_ips_data= Data(items=dst_ips)
 121        dst_ips_data.orderbyfreq()
 122        dst_ips_data.subtitle = plural("destination IP",
 123                len(dst_ips_data.items))
 124        dst_ips_data.truncl(config.prefs.getint("logparse", "maxlist"))
 125        section.append_data(dst_ips_data)
 126
 127        logger.info("Finished ufw section")
 128        return section
 129
 130    def check_dependencies(self):
 131        """
 132        Basic dependency check to determine if there are any logs to parse
 133        """
 134
 135        ufw_cmdline = "ufw --version"
 136        if self._check_dependency_command(ufw_cmdline)[0] != 0:
 137            return (False, ["ufw"])
 138        else:
 139            return (True, None)