description: Pipeline for IPTables
processors:
- set:
    field: event.ingested
    value: '{{_ingest.timestamp}}'
- grok:
    field: message
    patterns:
    - '%{SYSLOGTIMESTAMP:iptables.raw_date}%{SPACE}%{IPTABLES_HOSTNAME}%{GREEDYDATA}\[%{UBIQUITI_LABEL}\]%{IPTABLES}%{SPACE}'
    - '%{SYSLOGTIMESTAMP:iptables.raw_date}%{SPACE}%{IPTABLES_ACTION}%{GREEDYDATA}%{IPTABLES}%{SPACE}'
    - '%{SYSLOGTIMESTAMP:iptables.raw_date}%{SPACE}%{IPTABLES_HOSTNAME}%{SPACE}%{UDM_LOGS}%{IPTABLES_IP_PAYLOAD}'
    - '%{GREEDYDATA}\[%{UBIQUITI_LABEL}\]%{IPTABLES}%{SPACE}'
    - '%{GREEDYDATA}%{IPTABLES}%{SPACE}'
    pattern_definitions:
      IPTABLES_HOSTNAME: '%{HOSTNAME:observer.name}%{SPACE}(%{NOTSPACE}%{SPACE})?kernel:'
      IPTABLES_ACTION: '(:?%{WORD:event.action}:|%{IPTABLES_HOSTNAME}%{SPACE}iptables%{SPACE}%{WORD:event.action}|%{IPTABLES_HOSTNAME})'
      UNSIGNED_INT: '[0-9]+'
      ETHTYPE: (?:[A-Fa-f0-9]{2}):(?:[A-Fa-f0-9]{2})
      ETHTYPE_DISCARD: (?::[A-Fa-f0-9]{2})*
      NETFILTERMAC: (?:%{MAC:destination.mac}:%{MAC:source.mac}:%{ETHTYPE:iptables.ether_type}?%{ETHTYPE_DISCARD}|%{MAC:destination.mac}%{ETHTYPE_DISCARD}:%{ETHTYPE:iptables.ether_type}?)
      IPTABLES_ETHERNET: 'IN=%{DATA:iptables.input_device} OUT=%{DATA:iptables.output_device}?(?:
        MAC=%{NETFILTERMAC})?'
      IPTABLES_PORT_PAIR: SPT=%{UNSIGNED_INT:source.port:int} DPT=%{UNSIGNED_INT:destination.port:int}
      IPTABLES_TCP_FLAGS: (CWR |ECE |URG |ACK |PSH |RST |SYN |FIN )*
      IPTABLES_TCP_SEQ: SEQ=%{UNSIGNED_INT:iptables.tcp.seq:int} ACK=%{UNSIGNED_INT:iptables.tcp.ack:int}
      IPTABLES_TCP_DETAILS: (?:%{IPTABLES_TCP_SEQ} )?WINDOW=%{UNSIGNED_INT:iptables.tcp.window:int}
        RES=0x%{BASE16NUM:iptables.tcp_reserved_bits} %{IPTABLES_TCP_FLAGS:iptables.tcp.flags}
      IPTABLES_INCOMPLETE_PACKET: INCOMPLETE \[%{UNSIGNED_INT:iptables.incomplete_bytes:int}
        bytes\]
      IPTABLES_UDP_DETAILS: LEN=%{UNSIGNED_INT:iptables.udp.length:int}
      IPTABLES_ICMP_EXTRA_ECHO: ID=%{UNSIGNED_INT:iptables.icmp.id:int} SEQ=%{UNSIGNED_INT:iptables.icmp.seq:int}
      IPTABLES_ICMP_EXTRA_PARAM: PARAMETER=%{UNSIGNED_INT:iptables.icmp.parameter:int}
      IPTABLES_ICMP_EXTRA_REDIRECT: GATEWAY=%{IP:iptables.icmp.redirect}
      IPTABLES_ICMP_EXTRA: ( (?:%{IPTABLES_ICMP_EXTRA_ECHO}|%{IPTABLES_ICMP_EXTRA_PARAM}|%{IPTABLES_ICMP_EXTRA_REDIRECT}))*
      IPTABLES_ICMP_DETAILS: TYPE=%{UNSIGNED_INT:iptables.icmp.type:int} CODE=%{UNSIGNED_INT:iptables.icmp.code:int}((
        %{IPTABLES_INCOMPLETE_PACKET})|%{IPTABLES_ICMP_EXTRA})
      IPTABLES_PROTOCOL: PROTO=(?<network.transport>[a-zA-Z0-9]+)
      IPTABLES_IP_PAYLOAD: '%{IPTABLES_PROTOCOL}( %{IPTABLES_PORT_PAIR})?( (%{IPTABLES_TCP_DETAILS}|%{IPTABLES_UDP_DETAILS}|%{IPTABLES_ICMP_DETAILS}|%{IPTABLES_INCOMPLETE_PACKET}))?'
      IPTABLES_IP_FRAGFLAG: ((?<= )(CE|DF|MF))*
      IPTABLES_IP_START: 'SRC=%{IPV4:source.ip} DST=%{IPV4:destination.ip} LEN=%{UNSIGNED_INT:iptables.length:int}
        TOS=(0x)?%{BASE16NUM:iptables.tos} PREC=0x%{BASE16NUM:iptables.precedence_bits}
        TTL=%{UNSIGNED_INT:iptables.ttl:int} ID=%{UNSIGNED_INT:iptables.id:int}(?:
        %{IPTABLES_IP_FRAGFLAG:iptables.fragment_flags})?(?: FRAG: %{UNSIGNED_INT:iptables.fragment_offset:int})?'
      IPTABLES_IP: '%{IPTABLES_IP_START} %{IPTABLES_IP_PAYLOAD}'
      IPTABLES_IPV6_START: SRC=%{IPV6:source.ip} DST=%{IPV6:destination.ip} LEN=%{UNSIGNED_INT:iptables.length:int}
        TC=%{UNSIGNED_INT:iptables.tos} HOPLIMIT=%{UNSIGNED_INT:iptables.ttl:int}
        FLOWLBL=%{UNSIGNED_INT:iptables.flow_label:int}
      IPTABLES_IPV6: '%{IPTABLES_IPV6_START} %{IPTABLES_IP_PAYLOAD}'
      IPTABLES: '%{IPTABLES_ETHERNET} (:?%{IPTABLES_IP}|%{IPTABLES_IPV6})'
      UBIQUITI_FIELD: '[^-\]]*'
      UBIQUITI_RULESET_NAME: '[^\]]*'
      UBIQUITI_LABEL: '%{UBIQUITI_RULESET_NAME:iptables.ubiquiti.rule_set}-%{UBIQUITI_FIELD:iptables.ubiquiti.rule_number}-%{UBIQUITI_FIELD:event.action}'
      UDM_LOGS: '(%{UNSIGNED_INT}%{SPACE})?(TTL|TL|L)=(%{UNSIGNED_INT:iptables.ttl:int})%{SPACE}(ID=(%{UNSIGNED_INT:iptables.id:int})%{SPACE})?(DF%{SPACE})?'
- rename:
    field: message
    target_field: event.original
    if: ctx.event?.original == null
- grok:
    field: iptables.ubiquiti.rule_set
    ignore_missing: true
    ignore_failure: true
    patterns:
    - '%{UBIQUITI_FIELD:iptables.ubiquiti.input_zone}-%{UBIQUITI_FIELD:iptables.ubiquiti.output_zone}'
    pattern_definitions:
      UBIQUITI_FIELD: '[^-]*'
- date:
    if: ctx?.iptables?.raw_date != null && ctx.event.timezone == null
    field: iptables.raw_date
    formats:
    - MMM  d HH:mm:ss
    - MMM dd HH:mm:ss
    on_failure:
    - append:
        field: error.message
        value: '{{ _ingest.on_failure_message }}'
- date:
    if: ctx?.iptables?.raw_date != null && ctx.event.timezone != null
    field: iptables.raw_date
    formats:
    - MMM  d HH:mm:ss
    - MMM dd HH:mm:ss
    timezone: '{{ event.timezone }}'
    on_failure:
    - append:
        field: error.message
        value: '{{ _ingest.on_failure_message }}'
- remove:
    field: iptables.raw_date
    ignore_missing: true
- set:
    field: observer.name
    value: "{{hostname}}"
    ignore_empty_value: true
    if: ctx?.observer?.name == null
- set:
    field: observer.hostname
    value: "{{hostname}}"
    ignore_empty_value: true
    if: ctx?.observer?.name == null
- lowercase:
    field: network.transport
    ignore_missing: true
- lowercase:
    field: event.action
    ignore_missing: true
- geoip:
    field: source.ip
    target_field: source.geo
    ignore_missing: true
- geoip:
    field: destination.ip
    target_field: destination.geo
    ignore_missing: true
- geoip:
    database_file: GeoLite2-ASN.mmdb
    field: source.ip
    target_field: source.as
    properties:
    - asn
    - organization_name
    ignore_missing: true
- geoip:
    database_file: GeoLite2-ASN.mmdb
    field: destination.ip
    target_field: destination.as
    properties:
    - asn
    - organization_name
    ignore_missing: true
- rename:
    field: source.as.asn
    target_field: source.as.number
    ignore_missing: true
- rename:
    field: source.as.organization_name
    target_field: source.as.organization.name
    ignore_missing: true
- rename:
    field: destination.as.asn
    target_field: destination.as.number
    ignore_missing: true
- rename:
    field: destination.as.organization_name
    target_field: destination.as.organization.name
    ignore_missing: true
- script:
    lang: painless
    params:
      mappings:
      - source:
          object: iptables
          key: ether_type
        destination:
          object: network
          key: type
        map:
          08:00: ipv4
          86:dd: ipv6
      - source:
          object: event
          key: action
        destination:
          object: event
          key: action
        map:
          d: drop
          a: accept
      - source:
          object: event
          key: action
        destination:
          object: event
          key: type
        map:
          drop: denied
          accept: allowed
          deny: denied
          drop_input: denied
      - source:
          object: network
          key: transport
        destination:
          object: network
          key: transport
        map:
          icmpv6: ipv6-icmp
    source: >-
      for (action in params.mappings) {
        def src = ctx[action.source.object];
        if (src != null) {
          Map map = action.map;
          String key = src[action.source.key];
          String mapping = map[key];
          if (mapping != null) {
            Map dst = ctx[action.destination.object];
            if (dst == null) {
                dst = new HashMap();
                ctx[action.destination.object] = dst;
            }
            dst[action.destination.key] = mapping;
          }
        }
      }
- community_id:
    ignore_missing: true
    icmp_type: iptables.icmp.type
    icmp_code: iptables.icmp.code
- script:
    lang: painless
    params:
      hex_fields_to_convert:
      - ether_type
      - tos
      - precedence_bits
      - tcp_reserved_bits
    source: >-
      def iptables = ctx['iptables'];
      if (iptables != null) {
        for (key in params.hex_fields_to_convert) {
          long value = 0;
          def field = iptables[key];
          if (field == null) continue;
          char[] hex = field.toLowerCase().toCharArray();
          for (chr in hex) {
            long v = -1;
            if (chr >= (char) 'a' && chr <= (char) 'f') v = (long) chr - (char) 'a' + 10;
            else if (chr >= (char) '0' && chr <= (char) '9') v = (long) chr - (char) '0';
            if (v >= 0) {
              value = value * 16 + v;
            }
          }
          iptables[key] = value;
        }
      }

- set:
    field: event.kind
    value: event
- append:
    field: event.category
    value: network
- append:
    field: event.type
    value: connection
    if: "ctx?.source?.ip != null && ctx?.destination?.ip != null"
- append:
    field: related.ip
    value: "{{source.ip}}"
    if: "ctx?.source?.ip != null"
- append:
    field: related.ip
    value: "{{destination.ip}}"
    if: "ctx?.destination?.ip != null"
- rename:
    field: iptables.tcp_reserved_bits
    target_field: iptables.tcp.reserved_bits
    ignore_missing: true
- split:
    field: iptables.tcp.flags
    separator: "\\s+"
    ignore_missing: true
- split:
    field: iptables.fragment_flags
    separator: "\\s+"
    ignore_missing: true
- set:
    field: observer.egress.zone
    value: "{{iptables.ubiquiti.output_zone}}"
    ignore_empty_value: true
- set:
    field: observer.ingress.zone
    value: "{{iptables.ubiquiti.input_zone}}"
    ignore_empty_value: true
- set:
    field: rule.id
    value: "{{iptables.ubiquiti.rule_number}}"
    ignore_empty_value: true
- set:
    field: rule.name
    value: "{{iptables.ubiquiti.rule_set}}"
    ignore_empty_value: true
on_failure:
- set:
    field: error.message
    value: '{{ _ingest.on_failure_message }}'
