diff --git a/ipt-enable-logs/README.md b/ipt-enable-logs/README.md new file mode 100644 index 0000000..11dfbcd --- /dev/null +++ b/ipt-enable-logs/README.md @@ -0,0 +1,67 @@ +# Enable log tags on your UDM + +## Features + +If you're used to the Unifi Security Gateway, you may miss the USG log prefixes that allow you to know which rule blocked certain traffic. + +This mod adds logging prefixes to messages from `/var/log/messages` allowing you to trace a particular log message to the respective iptable rule (which is generated from the firewall rules you configure on the Network application, among other things) + +## Requirements + +1. You have successfully setup the on boot script described [here](https://github.com/boostchicken/udm-utilities/tree/master/on-boot-script) + +## General idea + +This mod builds a small Go program that modifies the existing iptables to add `--log-prefix` to entries that are defined as loggable through the `-j LOG` directive. The Go program is built in a Docker container local to the UDM. + +Here's an example snippet of an iptable modified by this program: + +``` +-A UBIOS_PREROUTING_USER_HOOK -p tcp -m set --match-set UBIOS_ADDRv4_eth8 dst -m tcp --dport 15060 -j LOG --log-prefix "[DNAT-PRER_U_HK-4294967310] " +-A UBIOS_PREROUTING_USER_HOOK -p tcp -m set --match-set UBIOS_ADDRv4_eth8 dst -m tcp --dport 15060 -m comment --comment 00000000004294967310 -j DNAT --to-destination 192.168.36.10:15060 +``` + +## Steps + +1. Copy [on_boot.d/30-ipt-enable-logs-launch.sh](./on_boot.d/30-ipt-enable-logs-launch.sh) to /mnt/data/on_boot.d +1. Copy the [scripts/ipt-enable-logs](./scripts/ipt-enable-logs) folder to /mnt/data/scripts +1. Copy [scripts/ipt-enable-logs.sh](./scripts/ipt-enable-logs.sh) to /mnt/data/scripts +1. Execute /mnt/data/on_boot.d/30-ipt-enable-logs-launch.sh +1. Copy [scripts/refresh-iptables.sh](./scripts/refresh-iptables.sh) to /mnt/data/scripts + +## Refreshing iptables + +Whenever you update the firewall rules on the Network application, the iptables will be reprovisioned and will need to be reprocessed +by calling /mnt/data/scripts/refresh-iptables.sh. + +## Looking at logs + +Logs can be followed easily from another machine through SSH by using the following bash functions: + +```shell +function logunifijson() { + ssh unifi "tail -f /var/log/messages" | \ + rg "kernel:" | \ + sed "s/]IN/] IN/" | \ + jq --unbuffered -R '. | rtrimstr(" ") | split(": ") | {date: (.[0] | split(" ") | .[0:3] | join(" "))} + (.[1] | capture("\\[.+\\] \\[(?.*)\\].*")) + ((.[1] | capture("\\[.+\\] (?.*)") | .rest | split(" ") | map(select(startswith("[") == false) | split("=") | {(.[0]): .[1]})) | (reduce .[] as $item ({}; . + $item)))' +} + +function logunifi() { + logunifijson | jq --unbuffered -r '"\(.date) - \(.rule)\tIN=\(.IN) \t\(.PROTO)\tSRC=\(.SRC)@\(.SPT)\tDST=\(.DST)@\(.DPT)\tLEN=\(.LEN)\t"' +} +``` + +Here's what the output of `logunifi` looks like: + +``` +Nov 14 10:58:31 - A-LAN_LOCAL_U-1097364144127 IN=br0 TCP SRC=192.168.16.10@55804 DST=192.168.16.1@443 LEN=52 +Nov 14 10:58:31 - A-LAN_LOCAL_U-1097364144127 IN=br0 TCP SRC=192.168.16.10@55804 DST=192.168.16.1@443 LEN=52 +Nov 14 10:58:31 - A-LAN_LOCAL_U-1097364144127 IN=br0 TCP SRC=192.168.16.10@55804 DST=192.168.16.1@443 LEN=52 +Nov 14 10:58:31 - A-LAN_LOCAL_U-1097364144127 IN=br0 TCP SRC=192.168.16.10@55804 DST=192.168.16.1@443 LEN=52 +Nov 14 10:58:31 - A-LAN_LOCAL_U-1097364144127 IN=br0 TCP SRC=192.168.16.10@55804 DST=192.168.16.1@443 LEN=52 +Nov 14 10:58:31 - A-LAN_LOCAL_U-1097364144127 IN=br0 TCP SRC=192.168.16.10@55804 DST=192.168.16.1@443 LEN=52 +``` + +## Acknowledgements + +Thanks a lot to [@opustecnica](https://github.com/opustecnica) for the [initial implementation](https://github.com/opustecnica/public/wiki/UDM-&-UDM-PRO-NOTES) and idea (based on a bash script)! diff --git a/ipt-enable-logs/on_boot.d/30-ipt-enable-logs-launch.sh b/ipt-enable-logs/on_boot.d/30-ipt-enable-logs-launch.sh new file mode 100755 index 0000000..2ac67ae --- /dev/null +++ b/ipt-enable-logs/on_boot.d/30-ipt-enable-logs-launch.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +if ! iptables-save | grep -e '\-A UBIOS_.* \--log-prefix "\[' > /dev/null; then + /mnt/data/scripts/ipt-enable-logs.sh | iptables-restore -c +else + echo "iptables already contains USER log prefixes, ignoring." +fi diff --git a/ipt-enable-logs/scripts/ipt-enable-logs.sh b/ipt-enable-logs/scripts/ipt-enable-logs.sh new file mode 100755 index 0000000..3ed4be9 --- /dev/null +++ b/ipt-enable-logs/scripts/ipt-enable-logs.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e + +docker run -it --rm -v /mnt/data/scripts/ipt-enable-logs:/src -w /src --network=none golang:1.17.3 go build -v -o /src/ipt-enable-logs /src >&2 + +/mnt/data/scripts/ipt-enable-logs/ipt-enable-logs diff --git a/ipt-enable-logs/scripts/ipt-enable-logs/go.mod b/ipt-enable-logs/scripts/ipt-enable-logs/go.mod new file mode 100644 index 0000000..bd22760 --- /dev/null +++ b/ipt-enable-logs/scripts/ipt-enable-logs/go.mod @@ -0,0 +1,3 @@ +module pedropombeiro.com/ipt-enable-logs + +go 1.17 diff --git a/ipt-enable-logs/scripts/ipt-enable-logs/main.go b/ipt-enable-logs/scripts/ipt-enable-logs/main.go new file mode 100644 index 0000000..7f8f815 --- /dev/null +++ b/ipt-enable-logs/scripts/ipt-enable-logs/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "strconv" + "strings" +) + +func main() { + cmd := exec.Command("iptables-save") + outputBytes, err := cmd.Output() + if err != nil { + _ = fmt.Errorf("Failed to run iptables-save: %v", err) + os.Exit(1) + } + + str := string(outputBytes) + lines := strings.Split(str, "\n") + re := regexp.MustCompile(`-A UBIOS_([A-Z_]+) .* --comment (\d+) -j ([A-Z]+)`) + + for i, line := range lines { + if i != 0 { + fmt.Println() + } + if !strings.HasSuffix(line, "-j LOG") { + fmt.Print(line) + continue + } + + matches := re.FindSubmatch([]byte(lines[i+1])) + commentNr, err := strconv.Atoi(string(matches[2])) + if err != nil { + commentNr = 0 + } + actionName := getActionName(string(matches[3])) + ruleName := getRuleName(string(matches[1]), commentNr) + fmt.Printf(`%s --log-prefix "[%s-%s] "`, line, actionName, ruleName) + } +} + +func getActionName(action string) string { + action = strings.Replace(action, "RETURN", "A", 1) + action = strings.Replace(action, "REJECT", "R", 1) + action = strings.Replace(action, "DROP", "D", 1) + action = strings.Replace(action, "MASQUERADE", "M", 1) + + return action +} + +func getRuleName(rule string, commentNr int) string { + rule = strings.Replace(rule, "PREROUTING", "PRER", 1) + rule = strings.Replace(rule, "POSTROUTING", "POSTR", 1) + rule = strings.Replace(rule, "HOOK", "HK", 1) + rule = strings.Replace(rule, "USER", "U", 1) + if commentNr != 0 { + rule = fmt.Sprintf("%s-%d", rule, commentNr) + } + return rule +} diff --git a/ipt-enable-logs/scripts/refresh-iptables.sh b/ipt-enable-logs/scripts/refresh-iptables.sh new file mode 100755 index 0000000..4983d40 --- /dev/null +++ b/ipt-enable-logs/scripts/refresh-iptables.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e + +if [ -f /mnt/data/on_boot.d/10-dns.sh ]; then + if ! iptables-save | grep -e '\-A PREROUTING.* \--log-prefix "\[' > /dev/null; then + /mnt/data/on_boot.d/10-dns.sh + else + echo "iptables already contains DNAT log prefixes, ignoring." + fi +fi + +/mnt/data/on_boot.d/30-ipt-enable-logs-launch.sh