|
| 1 | +#!/bin/sh |
| 2 | +# Adds an IP to a nftables set |
| 3 | +# Requirements: Linux with nftables |
| 4 | +# Expect: srcip |
| 5 | +# Author: Daniel B. Cid (iptables) |
| 6 | +# Author: cgzones |
| 7 | +# Author: ChristianBeer |
| 8 | +# Last modified: Dec 26, 2021 |
| 9 | + |
| 10 | +# You need to create a set and a rule that uses it in order for packets to be dropped |
| 11 | +# make sure the drop rules are checked before the accept rules |
| 12 | +# Example: |
| 13 | +# nft add set inet filter ossec_ar4 { type ipv4_addr\; comment \"ossec active response\" \; } |
| 14 | +# nft add set inet filter ossec_ar6 { type ipv6_addr\; comment \"ossec active response\" \; } |
| 15 | +# nft add rule inet filter input ip saddr @ossec_ar4 drop |
| 16 | +# nft add rule inet filter input ip6 saddr @ossec_ar6 drop |
| 17 | + |
| 18 | + |
| 19 | +UNAME=`uname` |
| 20 | +ECHO="/bin/echo" |
| 21 | +GREP="/bin/grep" |
| 22 | +NFTCMD="/usr/sbin/nft" |
| 23 | +RULE="" |
| 24 | +ARG1="" |
| 25 | +# protocol family used in nftables configuration |
| 26 | +FAMILYIPV4="inet" # use "ip" when you have separate tables for ipv4 and ipv6 |
| 27 | +FAMILYIPV6="inet" # use "ip6" when you have separate tables for ipv4 and ipv6 |
| 28 | +# nftables table name |
| 29 | +TABLEIPV4="filter" |
| 30 | +TABLEIPV6="filter" |
| 31 | +# Name of the sets where offending IPs should be added |
| 32 | +SETIPV4="ossec_ar4" |
| 33 | +SETIPV6="ossec_ar6" |
| 34 | + |
| 35 | +RULEID="" |
| 36 | +ACTION=$1 |
| 37 | +USER=$2 |
| 38 | +IP=$3 |
| 39 | +PWD=`pwd` |
| 40 | +LOCK="${PWD}/nft-drop" |
| 41 | +LOCK_PID="${PWD}/nft-drop/pid" |
| 42 | + |
| 43 | + |
| 44 | +LOCAL=`dirname $0`; |
| 45 | +cd $LOCAL |
| 46 | +cd ../ |
| 47 | +filename=$(basename "$0") |
| 48 | + |
| 49 | +LOG_FILE="${PWD}/../logs/active-responses.log" |
| 50 | + |
| 51 | +echo "`date` $0 $1 $2 $3 $4 $5" >> ${LOG_FILE} |
| 52 | + |
| 53 | + |
| 54 | +# Checking for an IP |
| 55 | +if [ "x${IP}" = "x" ]; then |
| 56 | + echo "$0: <action> <username> <ip>" |
| 57 | + exit 1; |
| 58 | +fi |
| 59 | + |
| 60 | +case "${IP}" in |
| 61 | + *:* ) RULE="element ${FAMILYIPV6} ${TABLEIPV6} ${SETIPV6} { ${IP} }";; |
| 62 | + *.* ) RULE="element ${FAMILYIPV4} ${TABLEIPV4} ${SETIPV4} { ${IP} }";; |
| 63 | + * ) echo "`date` Unable to run active response (invalid IP: '${IP}')." >> ${LOG_FILE} && exit 1;; |
| 64 | +esac |
| 65 | + |
| 66 | +# This number should be more than enough (even if a hundred |
| 67 | +# instances of this script is ran together). If you have |
| 68 | +# a really loaded env, you can increase it to 75 or 100. |
| 69 | +MAX_ITERATION="50" |
| 70 | + |
| 71 | +# Lock function |
| 72 | +lock() |
| 73 | +{ |
| 74 | + i=0; |
| 75 | + # Providing a lock. |
| 76 | + while [ 1 ]; do |
| 77 | + mkdir ${LOCK} > /dev/null 2>&1 |
| 78 | + MSL=$? |
| 79 | + if [ "${MSL}" = "0" ]; then |
| 80 | + # Lock acquired (setting the pid) |
| 81 | + echo "$$" > ${LOCK_PID} |
| 82 | + return; |
| 83 | + fi |
| 84 | + |
| 85 | + # Getting currently/saved PID locking the file |
| 86 | + C_PID=`cat ${LOCK_PID} 2>/dev/null` |
| 87 | + if [ "x" = "x${S_PID}" ]; then |
| 88 | + S_PID=${C_PID} |
| 89 | + fi |
| 90 | + |
| 91 | + # Breaking out of the loop after X attempts |
| 92 | + if [ "x${C_PID}" = "x${S_PID}" ]; then |
| 93 | + i=`expr $i + 1`; |
| 94 | + fi |
| 95 | + |
| 96 | + sleep $i; |
| 97 | + |
| 98 | + i=`expr $i + 1`; |
| 99 | + |
| 100 | + # So i increments 2 by 2 if the pid does not change. |
| 101 | + # If the pid keeps changing, we will increments one |
| 102 | + # by one and fail after MAX_ITERACTION |
| 103 | + |
| 104 | + if [ "$i" = "${MAX_ITERATION}" ]; then |
| 105 | + kill="false" |
| 106 | + for pid in `pgrep -f "${filename}"`; do |
| 107 | + if [ "x${pid}" = "x${C_PID}" ]; then |
| 108 | + # Unlocking and exiting |
| 109 | + kill -9 ${C_PID} |
| 110 | + echo "`date` Killed process ${C_PID} holding lock." >> ${LOG_FILE} |
| 111 | + kill="true" |
| 112 | + unlock; |
| 113 | + i=0; |
| 114 | + S_PID=""; |
| 115 | + break; |
| 116 | + fi |
| 117 | + done |
| 118 | + |
| 119 | + if [ "x${kill}" = "xfalse" ]; then |
| 120 | + echo "`date` Unable kill process ${C_PID} holding lock." >> ${LOG_FILE} |
| 121 | + # Unlocking and exiting |
| 122 | + unlock; |
| 123 | + exit 1; |
| 124 | + fi |
| 125 | + fi |
| 126 | + done |
| 127 | +} |
| 128 | + |
| 129 | +# Unlock function |
| 130 | +unlock() |
| 131 | +{ |
| 132 | + rm -rf ${LOCK} |
| 133 | +} |
| 134 | + |
| 135 | + |
| 136 | + |
| 137 | +# Blocking IP |
| 138 | +if [ "x${ACTION}" != "xadd" -a "x${ACTION}" != "xdelete" ]; then |
| 139 | + echo "$0: invalid action: ${ACTION}" |
| 140 | + exit 1; |
| 141 | +fi |
| 142 | + |
| 143 | + |
| 144 | + |
| 145 | +# We should run on linux |
| 146 | +if [ "X${UNAME}" = "XLinux" ]; then |
| 147 | + if [ "x${ACTION}" = "xadd" ]; then |
| 148 | + ARG1="add" |
| 149 | + else |
| 150 | + ARG1="delete" |
| 151 | + fi |
| 152 | + |
| 153 | + # Checking if nft is present |
| 154 | + if [ ! -x ${NFTCMD} ]; then |
| 155 | + NFTCMD="/usr"${NFTCMD} |
| 156 | + if [ ! -x ${NFTCMD} ]; then |
| 157 | + echo "$0: can not find nft" |
| 158 | + exit 1; |
| 159 | + fi |
| 160 | + fi |
| 161 | + |
| 162 | + # Executing and exiting |
| 163 | + COUNT=0; |
| 164 | + lock; |
| 165 | + while [ 1 ]; do |
| 166 | + ${NFTCMD} ${ARG1} ${RULE} >/dev/null |
| 167 | + RES=$? |
| 168 | + if [ $RES = 0 ]; then |
| 169 | + break; |
| 170 | + else |
| 171 | + COUNT=`expr $COUNT + 1`; |
| 172 | + echo "`date` Unable to run (nft returning $RES): $COUNT - $0 $1 $2 $3 $4 $5" >> ${LOG_FILE} |
| 173 | + sleep $COUNT; |
| 174 | + |
| 175 | + if [ $COUNT -gt 4 ]; then |
| 176 | + break; |
| 177 | + fi |
| 178 | + fi |
| 179 | + done |
| 180 | + unlock; |
| 181 | + |
| 182 | + exit 0; |
| 183 | +else |
| 184 | + exit 0; |
| 185 | +fi |
0 commit comments