This post will be edited over time, please feel free to come back and check for new content.

Last edit: 11-25-2024

Goal: A HomeLab setup that protects itself

This example HomeLab has at its core an OPNSense Router, smart switches with subnet zones, several VMs, a few Docker environments, and specifically for this version of this post: with cron updating near all Rules enabled Suricata, and after design of Zones, Firewall rules, Port Forwards, and the like, we installed OPNSense’s CrowdSec plugin.

Once installed, as we will have additional agents on the various services in your HomeLab, we will likely want to transition from the SQLite DB to a Postgres DB or something similar.

Near all additional services are actually deployed as Docker Stacks or Containers.

Further, we have designed this using MACVLAN as we will have an IDS involved and wish to parse the network traffic as is via Suricata. Extra to note, we designed this with an SSL Terminating Reverse Proxy in front of some of the publicly available ‘HomeLab Services’ allowing Suricata do see everything.

The DB has been setup on a subnet adjacent Docker environment, and I’ve used the ‘Custom LAPI’ config option in the OPNSense GUI CrowdSec plugin settings page, plus a persistent ‘/usr/local/etc/crowdsec/config.yaml.local’ that CrowdSec supports (it only replaces the configs in the original config.yaml with what you set in it). Will expand on this portion as it is likely of hot interest shortly as it also enables the rest of the Multi-Server setup. The ‘config.yaml.local’ and CrowdSec and a Postgres DB with Multi-Server should get you close on a Google search, I will update this detail on this page in time.

Now we are going to skip a few details about the beginning, many middle sections, and reach the most recent realization….

Suricata EVE without payload perfect for CrowdSec Parser

The ‘printable_payload’ element of the JSON output of eve.json is a bit much for most any parser, with this in mind, it is highly advisable to enable an additional EVE output from Suricata without this field for CrowdSec’s agent to easily parse the state and respond quickly.

In the example below we demonstrate doing so with an overwrite setting for the Reverse Proxy that is part of the network stack.

To do this, persistently, you must create or edit the following files on your OPNSense router

Step 1

The OPNSense CrowdSec plugin installs observing a few default logs from OPNSense (lighttpd/sshd/pf) but does not come configured for any Suricata log listening. A CrowdSec Acquis file must be created or modified to get the feature we are adding here and without this, the evexff.json file will go unobserved, you will also need the CrowdSec Hub elements to enable the parsing/alerting for Suricata, so console into the OPNSense and enter the following command (ssh in, select option ‘8’):

cscli collections install crowdsecurity/suricata
cscli collections install crowdsecurity/whitelist-good-actors
cscli parsers install crowdsecurity/whitelists

There is an additional file that will help prevent you from banning yourself from yourself, make the next file and paste the content.

/usr/local/etc/crowdsec/postoverflow/s01-whitelist/mydomain-whitelist.yaml:

name: my/whitelist ## Must be unqiue                                      
description: "Whitelist events from my dynamic IP"                              
whitelist:                                                                      
  reason: "My dynamic IP"                                                       
  expression:                                                                   
      - evt.Overflow.Alert.Source.IP in LookupHost("my.domain.com")

Then after creating the following file, it is my recommendation to hit ‘save’ on the CrowdSec plugin GUI of your OPNSense, this appears to reload instead of restarting the service as desired.

/usr/local/etc/crowdsec/acquis.d/suricata.yaml:

---
filenames:
  - /var/log/suricata/evexff.json
labels:
  type: suricata-evelogs
---

Step 2

Now that CrowdSec is aware and ‘listening’ if you will, we will want to create, and rotate those evexff.json logs, let’s setup the rotation of ‘/var/log/suricata/evexff.json’ as a custom config

/usr/local/etc/newsyslog.conf.d/suricataxff.conf:

# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num]
/var/log/suricata/evexff.json      root:wheel      640     1       500000  $W0D23  B       /var/run/suricata.pid   1

Step 3

To note: It appears the ‘custom.yaml‘ file you will likely edit, needs the entire ‘output:’ stanza of the original (/usr/local/etc/suricata/suricata.yaml) Suricata config and then edit as desired – the example below is the working ‘custom.yaml’ with the upgraded edit being the additional EVE ouput.

Worst case currently, an admin will have to be aware of updates to the original and diff the two wisely, as I develop that I will share here. It appears like you replace at ‘stanza’ level, so the other features (threading/etc.) of Suricata appear to be performing as expected, but, my experience on this feels suddenly fresher than it used to. It would possibly be better to have this config at the surface of the OPNSense available to the same spaces as the ‘EVE’ logs for Suricata. Might try to develop/contribute in the future – barely have time for this post lol. ^_^

/root/custom.yaml (/usr/local/etc/suricata/custom.yaml – more on this later):

%YAML 1.1
---
vars:
  address-groups:
    HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"
    EXTERNAL_NET: "!$HOME_NET"
    HTTP_SERVERS: "$HOME_NET"
    SMTP_SERVERS: "$HOME_NET"
    SQL_SERVERS: "$HOME_NET"
    DNS_SERVERS: "$HOME_NET"
    TELNET_SERVERS: "$HOME_NET"
    AIM_SERVERS: "$EXTERNAL_NET"
    DC_SERVERS: "$HOME_NET"
    DNP3_SERVER: "$HOME_NET"
    DNP3_CLIENT: "$HOME_NET"
    MODBUS_CLIENT: "$HOME_NET"
    MODBUS_SERVER: "$HOME_NET"
    ENIP_CLIENT: "$HOME_NET"
    ENIP_SERVER: "$HOME_NET"
    SIP_SERVERS: "$HOME_NET"
    INTERNAL_DEVICELIST: "[10.30.16.41,10.30.16.42,10.30.16.43]"
  port-groups:
    HTTP_PORTS: "[80,8080]"
    SHELLCODE_PORTS: "!80"
    ORACLE_PORTS: 1521
    SSH_PORTS: 22
    DNP3_PORTS: 20000
    MODBUS_PORTS: 502
    FILE_DATA_PORTS: "[$HTTP_PORTS,110,143]"
    FTP_PORTS: 21
    GENEVE_PORTS: 6081
    VXLAN_PORTS: 4789
    TEREDO_PORTS: 3544
    SIP_PORTS: "[5060,5061]"
outputs:
  - eve-log:
      enabled: yes
      filetype: regular #regular|syslog|unix_dgram|unix_stream|redis
      filename: evexff.json
      xff:
        enabled: yes
        mode: overwrite
        deployment: reverse
        header: X-Forwarded-For
      types:
        - alert:
            payload: no
            payload-buffer-size: 100kb
            payload-printable: no
            metadata: yes             # enable inclusion of app layer metadata with alert. Default yes
            tagged-packets: yes
  - eve-log:
      enabled: yes
      filetype: regular #regular|syslog|unix_dgram|unix_stream|redis
      filename: eve.json
      pcap-file: false
      community-id: true
      community-id-seed: 0
      xff:
        enabled: yes
        mode: extra-data
        deployment: reverse
        header: X-Forwarded-For
      types:
        - alert:
            payload: no
            payload-buffer-size: 100kb
            payload-printable: yes
            metadata:
              app-layer: true
              flow: true
              rule:
                metadata: true
                raw: true
            tagged-packets: yes
        - alert:
            payload: yes
            payload-buffer-size: 100kb
            payload-printable: yes
            metadata: yes             # enable inclusion of app layer metadata with alert. Default yes
            metadata:
              app-layer: true
              rule:
                metadata: true
                raw: true
            tagged-packets: yes
        - frame:
            enabled: no
        - anomaly:
            enabled: no
  - eve-log:
      enabled: yes
      type: syslog
      identity: "suricata"
      facility: local5
      level: Info
      community-id: true
      community-id-seed: 0
      xff:
        enabled: yes
        mode: extra-data
        deployment: reverse
        header: X-Forwarded-For
      types:
        - alert:
            payload: no
            payload-buffer-size: 100kb
            payload-printable: yes
            metadata:
              app-layer: true
              flow: true
              rule:
                metadata: true
                raw: true
            tagged-packets: yes
        - frame:
            enabled: no
        - anomaly:
            enabled: no
            types:
              applayer: no
  - unified2-alert:
      enabled: no
  - http-log:
      enabled: no
      filename: http.log
      append: yes
  - tls-log:
      enabled: no  # Log TLS connections.
      filename: tls.log # File to store TLS logs.
      append: yes
  - tls-store:
      enabled: no
  - pcap-log:
      enabled: no
      filename: log.pcap
      limit: 1000mb
      max-files: 2000
      compression: none
      mode: normal # normal, multi or sguil.
      use-stream-depth: no #If set to "yes" packets seen after reaching stream inspection depth are ignored. "no" logs all packets
      honor-pass-rules: no # If set to "yes", flows in which a pass rule matched will stop being logged.
  - alert-debug:
      enabled: no
      filename: alert-debug.log
      append: yes
  - alert-prelude:
      enabled: no
      profile: suricata
      log-packet-content: no
      log-packet-header: yes
  - stats:
      enabled: yes
      filename: stats.log
      append: yes       # append to file (yes) or overwrite it (no)
      totals: yes       # stats for all threads merged together
      threads: no       # per thread stats
  - file-store:
      version: 2
      enabled: no
      xff:
        enabled: no
        mode: extra-data
        deployment: reverse
        header: X-Forwarded-For
  - file-store:
      enabled: no
  - tcp-data:
      enabled: no
      type: file
      filename: tcp-data.log
  - http-body-data:
      enabled: no
      type: file
      filename: http-data.log
  - lua:
      enabled: no
      scripts:
app-layer:
  protocols:
    telnet:
      enabled: yes
    rfb:
      enabled: yes
      detection-ports:
        dp: 5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909
    mqtt:
      enabled: yes
    krb5:
      enabled: yes
    bittorrent-dht:
      enabled: yes
    snmp:
      enabled: yes
    ike:
      enabled: yes
    tls:
      enabled: yes
      detection-ports:
        dp: 443
      ja3-fingerprints: auto
    pgsql:
      enabled: yes
      stream-depth: 0
      max-tx: 1024
    dcerpc:
      enabled: yes
    ftp:
      enabled: yes
    rdp:
    ssh:
      enabled: yes
    http2:
      enabled: yes
    smtp:
      enabled: yes
      raw-extraction: no
      mime:
        decode-mime: yes
        decode-base64: yes
        decode-quoted-printable: yes
        header-value-depth: 2000
        extract-urls: yes
        body-md5: no
      inspected-tracker:
        content-limit: 100000
        content-inspect-min-size: 32768
        content-inspect-window: 4096
    imap:
      enabled: detection-only
    smb:
      enabled: yes
      detection-ports:
        dp: 139, 445
    nfs:
      enabled: yes
    tftp:
      enabled: yes
    dns:
      tcp:
        enabled: yes
        detection-ports:
          dp: 53
      udp:
        enabled: yes
        detection-ports:
          dp: 53
    http:
      enabled: yes
      libhtp:
         default-config:
           personality: IDS
           request-body-limit: 100kb
           response-body-limit: 100kb
           request-body-minimal-inspect-size: 32kb
           request-body-inspect-window: 4kb
           response-body-minimal-inspect-size: 40kb
           response-body-inspect-window: 16kb
           response-body-decompress-layer-limit: 2
           http-body-inline: auto
           swf-decompression:
             enabled: no
             type: both
             compress-depth: 100kb
             decompress-depth: 100kb
           double-decode-path: no
           double-decode-query: no
         server-config:
    modbus:
      enabled: yes
      detection-ports:
        dp: 502
      stream-depth: 0
    dnp3:
      enabled: yes
      detection-ports:
        dp: 20000
    enip:
      enabled: yes
      detection-ports:
        dp: 44818
        sp: 44818
    ntp:
      enabled: yes
    quic:
      enabled: yes
    dhcp:
      enabled: yes
    sip:
      enabled: yes
asn1-max-frames: 256
datasets:
  defaults:
  rules:
host-os-policy:
  windows: []
  bsd: []
  bsd-right: []
  old-linux: []
  linux: []
  old-solaris: []
  solaris: []
  hpux10: []
  hpux11: []
  irix: []
  macos: []
  vista: []
  windows2k3: []
netmap:                                                                         
 - interface: default                                                           
   threads: auto                                                                
   copy-mode: ips                                                               
   disable-promisc: no #  promiscuous mode                                      
   checksum-checks: auto                                                        
   bpf-filter: not (( host 10.30.1.6 or 10.30.1.3 ) or ( net 10.30.14.0/24 or 10.30.21.0/24 ))
 - interface: vlan0.1.10
   copy-iface: vlan0.1.10^
 - interface: vlan0.1.10^
   copy-iface: vlan0.1.10
 - interface: vlan0.1.50
   copy-iface: vlan0.1.50^
 - interface: vlan0.1.50^
   copy-iface: vlan0.1.50
 - interface: igb0
   copy-iface: igb0^
 - interface: igb0^
   copy-iface: igb0
 - interface: ix1
   copy-iface: ix1^
 - interface: ix1^
   copy-iface: ix1
default-rule-path: /usr/local/etc/suricata/opnsense.rules
rule-files:
  - suricata.rules
classification-file: /usr/local/etc/suricata/classification.config
reference-config-file: /usr/local/etc/suricata/reference.config
threshold-file: /usr/local/etc/suricata/threshold.config

To Note: My learning on “Netmap”, BPF, and Suricata is only just getting out of beginner/intermediate, not sure if the Netmap bits are doing things given that I have OPNSense set to IDS mode, but, I want to enable the BPF without having to modify the service file for Suricata in OPNSense, so far, this appears to be the best way. Do not blindly copy all of this as your “custom.yaml” the Netmap portion is going to be unique to YOUR router and you will have to ‘shim’ in the mod yourself.

Step 4

This part is the coming soon, I will provide a link to the Post at the Forum where I am figuring out how to resolve the nature of having more control over the Suricata configuration as well as, how to use suricata-update to keep the IDS rules files in order (and modified the way I want them).

OPNSense Forum:
https://forum.opnsense.org/index.php?topic=44128.0

Suricata Forum:
https://forum.suricata.io/t/slow-suricata-update-on-an-opnsense-router-takes-30-minutes-for-200k-rules/5068

HomeLab : Series

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.