[Ubuntu 20.04] Swatchで動的ファイヤーウォール（IDS）構築

# swatchdog のインストール

　Ubuntu で Swatch IDS を構築し、不正アクセスを防止する。
　必要なパッケージをインストール。

```bash
apt install swatch sleepenh perl
```

　おそらく Perl の file-tail もこれで一緒にインストールされるはず。

# 起動スクリプトを作成する

　Swatch はただのコマンドなので、設定ファイルをもとに一括で起動するスクリプトを作成し、systemd へ登録する。使われてないが一応ロックファイルも作成する仕様にしている。

```bash
vi /usr/local/bin/swatch
```

```bash:/usr/local/bin/swatch
#!/bin/bash

### BEGIN INIT INFO
# Provides:             swatch
# Short-Description:    Start Stop Swatchdog Daemon
# Default-Start:        2 3 4 5
# Default-Stop:
### END INIT INFO

PATH=/bin:/usr/sbin:/usr/bin
LANG=C

. /lib/lsb/init-functions

DAEMON=/usr/local/bin/swatch

test -x $DAEMON || exit 1

case "$1" in
    start)
        ls /tmp/.swatchdog_script.* > /dev/null 2>&1
        if [ $? -ne 0 ]; then
            for conf in /etc/swatch/*.conf
            do
                WATCHLOG=`grep "^# logfile" $conf | awk '{ print $3 }'`
                swatchdog --config-file $conf --tail-file $WATCHLOG \
                --script-dir=/tmp --awk-field-syntax --use-cpan-file-tail --daemon \
                >> /var/log/swatch/swatch.log 2>&1
                echo "Starting Swatchdog... $conf $WATCHLOG" >> /var/log/swatch/swatch.log
                if [ $? -ne 0 ]; then
                        echo "`date` Script error. Swatchdog couldn't start." >> /var/log/swatch/swatch.log
                        echo "  Script error. Swatchdog couldn't start."
                        exit 1
                fi
            done
                [ $? = 0 ] && touch /var/lock/subsys/swatch
            echo "`date` Swatchdog Daemon has started." >> /var/log/swatch/swatch.log
            echo "  Swatchdog Daemon has started."
        else
            echo "  Swatchdog Daemon already has started."
        fi
        ;;
    stop)
        ls /tmp/.swatchdog_script.* > /dev/null 2>&1
        if [ $? -eq 0 ]; then
            echo "`date` Shutting down Swatchdog Daemon." >> /var/log/swatch/swatch.log
            echo "  Shutting down Swatchdog Daemon..."
            for pid in `ps aux | grep swatchdog | grep -v grep | awk '{print $2}'`
                do
                    kill $pid
                done
            rm -f /var/lock/subsys/swatch /tmp/.swatchdog_script.*
        else
            echo "  Swatchdog Daemon is not running."
        fi
        ;;
    restart)
        /usr/local/bin/swatch stop
        /usr/local/bin/swatch start
        ;;
    status)
        ls /tmp/.swatchdog_script.* > /dev/null 2>&1
        if [ $? -eq 0 ]; then
            echo -e "\n  Swatchdog Daemon is monitering as following pid...\n"
            ps aux | grep swatchdog | grep -v grep | awk '{print $2,$9,$10,$11,$12,$13,$14,$15,$16}'
        else
            echo -e "\n  Swatchdog Daemon is not running.\n"
        fi
        ;;
    *)
        echo "Bad usage. Please use swatch command with... {start|stop|restart|status}."
        exit 1
esac

exit 0
```

# systemd へ登録する

　systemd への登録。systemd は最終生成された子プロセスのみを監視する仕様らしいので、type は oneshot としている。

```bash
vi /etc/systemd/system/swatch.service
```

```bash:/etc/systemd/system/swatch.service
[Unit]
Description=Swatchdog Log Monitering Daemon

[Service]
ExecStart=/usr/local/bin/swatch start
ExecReload=/usr/local/bin/swatch restart
ExecStop=/usr/local/bin/swatch stop
Restart=no
Type=oneshot
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
```

# swatch のアクションスクリプトを書く

　続いて各種ログから不正アクセスチャレンジを検出した際に実行するスクリプトを作成。ロックしたIPの解除は敢えて変則的な36時間後に設定。24時間とか48時間とかでも良い。

　2023年3月、話題のChatGPTさんに校正校閲改善してもらいました。ただし、`BASH_REMATCH`を使っているので、1行のログに複数のIPアドレスがあった場合は最後にマッチしたものとなります。

```bash
vi /usr/local/bin/swatch_action.sh
```

```bash:/usr/local/bin/swatch_action.sh
#!/bin/bash

PATH=/bin:/sbin:/usr/bin
LANG=C

# ログを標準入力から取得
read LOG

# ログからIPアドレスらしきものを抽出
if [[ $LOG =~ (([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]) ]]; then
    IPADDR=${BASH_REMATCH[0]}
else
    echo "`date` [swatch_action.sh:Couldn't get IPADDR] $LOG" >> /var/log/swatch/IPerr.log
    exit
fi

# 自固定グローバルIP（環境に応じて変更）
MYIP="xxx.xxx.xxx.xxx"

# IPが指定したものであれば終了（ホワイトリスト）
if [ "$IPADDR" = "$MYIP" ]; then
    exit
fi

# 拒否リストにIPが含まれている場合は終了
TargetIP=`iptables -w -L INPUT -n | grep DROP.*all | grep " $IPADDR "`
sleepenh 0.5 > /dev/null 2>&1
if [ -n "$TargetIP" ]; then
  exit
fi

# IPアドレスが取得できた場合の処理はここから
# IPアドレスがプライベートIPアドレスの場合は終了
if [[ "$IPADDR" = "127.0.0.1" || "$IPADDR" =~ ^10\. || "$IPADDR" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. || "$IPADDR" =~ ^192\.168\. || "$IPADDR" =~ \.0$ ]]; then
    exit
fi

# 以下IPTABLESに登録がない場合の処理
# ログをアドレス別ログに記録し、行数をカウント
echo "$LOG" >> /var/log/swatch/$IPADDR
cnt=`cat /var/log/swatch/$IPADDR | wc -l`

# アクセスしてきたIPのログが5行以上または引数でlockを指定されている場合登録処理
if [ $cnt -ge 5 ] || [ "$1" = "lock" ]; then

### IPTABLESに登録
    iptables -w -I INPUT -s $IPADDR -j DROP

### 解除予定（36時間後）を予約
    echo "iptables -D INPUT -s $IPADDR -j DROP > /dev/null 2>&1; rm -f /var/log/swatch/$IPADDR > /dev/null 2>&1" | at now+36hour > /dev/null 2>&1
    echo "`date` $IPADDR locked. Release schedule at `date --date '36hours' +'%Y/%m/%d %H:%M:%S'`." >> /var/log/swatch/swatch.log
fi
```

　実行権限を与える。

```bash
chmod 755 /usr/local/bin/swatch_action.sh
```

　実際の環境では、ログ（ソフト）ごとに専用のスクリプトを作成し運用している。また、ブロックする時間も1500時間以上ブロックしている。スクリプトと設定ルールの書き方で本来ブロックしたくないIPがブロックされてしまったりするので、運用の経過を注意して確認し、目的とするIPだけがブロックされるように調整する。

## IPアドレスを抽出する別の方法。

　ChatGPTさんいわく、`grep`を使うより組み込み機能を使った方が速いよ、との事。でも必ず最後にマッチしたものが対象とするIPとは限らない。ログのフォーマットに合わせてスクリプトを記述する。

```:以下でも抽出できるが、複数マッチする場合がある
IPADDR=`echo $LOG | grep -o "\(\([1-9]\?[0-9]\|1[0-9]\{2\}\|2[0-4][0-9]\|25[0-5]\)\.\)\{3\}\([1-9]\?[0-9]\|1[0-9]\{2\}\|2[0-4][0-9]\|25[0-5]\)"`
```


## 設定の例

　設定ファイルの例。/etc に swatch ディレクトリがなければ mkdir で作成。設定ファイルは xxx.conf で作成したものを読み込むように起動スクリプト中に記載している。

### Postfix, Dovecot の不正アクセス対策の例。

```bash
vi /etc/swatch/mail.conf
```


```bash:/etc/swatch/mail.conf
# logfile /var/log/mail.log

watchfor /warning.*authentication failed/
  pipe "/usr/local/bin/swatch_action.sh"
  throttle=00:00:20

watchfor /warning.*verification failed/
  pipe "/usr/local/bin/swatch_action.sh"
  throttle=00:00:20

watchfor /Aborted login.*failed/
  pipe "/usr/local/bin/swatch_action.sh"
  throttle=00:00:20

watchfor /Disconnected.*auth failed/
  pipe "/usr/local/bin/swatch_action.sh"
  throttle=00:00:20

watchfor /NOQUEUE.*reject.*Relay access denied/
  pipe "/usr/local/bin/swatch_action.sh"
  throttle=00:00:20
```

### apache への不正アクセス対策の例。

```bash
vi /etc/swatch/httpd.conf
```

```bash:/etc/swatch/httpd.conf
# logfile /var/log/apache2/access.log

# PHPMyadminへのアクセスを試みるipを規制（必要な人は書いちゃダメ）
watchfor /phpmyadmin/i
    pipe "/usr/local/bin/swatch_action.sh lock"
    throttle=00:00:30

# MySQL関連への不正ログインを試みるipを規制（必要な人は書いちゃダメ）
watchfor /mysql/i
    pipe "/usr/local/bin/swatch_action.sh lock"
    throttle=00:00:30

# Directory traversal
watchfor /etc.*passwd/i
    pipe "/usr/local/bin/swatch_action.sh lock"
    throttle=00:00:30

watchfor /proc.*self/i
    pipe "/usr/local/bin/swatch_action.sh lock"
    throttle=00:00:30
```


# ログ関連

　ログ用のディレクトリとファイルを作成する。所有者は root:root 、パーミッションは 644（デフォルト）でOK。

```bash
mkdir /var/log/swatch
chmod 655 /var/log/swatch
touch /var/log/swatch/swatch.log
touch /var/log/swatch/IPerr.log
touch /var/log/swatch/DMerr.log
```

　ログローテ設定。

```bash
vi /etc/logrotate.d/swatch
```

```bash:/etc/logrotate.d/swatch
/var/log/swatch/swatch.log
/var/log/swatch/DMerr.log
/var/log/swatch/IPerr.log
{
    weekly
    rotate 8
    missingok
    notifempty
    sharedscripts
    postrotate
        /usr/local/bin/swatch restart
    endscript
}
```

# 重複や漏れ等の整合性維持を自動化する

　`iptables`と`atジョブ`の整合性を保つスクリプトを作成。なぜかわからないが、片方だけにしか登録されていないことがしばしばある。

```bash
vi atcheck.sh
```

```bash:atcheck.sh
#!/bin/bash

##################################################################
# swatchの動作と連携して問題を未然に防ぐスクリプト
# 4時間ごとにcronでの定期実行、サーバー再起動後、iptables再設定後に実行を推奨
##################################################################

PATH=/bin:/usr/sbin:/usr/bin
LANG=C

####################################################################
# ATjobに重複して解除登録しているIPのジョブを削除
# 後に登録した方を優先し、先に動くジョブが削除対象
####################################################################

IParray=()

for K in `atq | sort -r | awk '{print $1}'`
do
    IPADDR=`at -c ${K} | grep iptables | cut -d " " -f 5`
    Ovl=`echo "${IParray[@]}" | grep " $IPADDR "`
    if [ -n "$Ovl" ]; then
        atrm $K > /dev/null 2>&1
    else
        IParray+=" $IPADDR "
    fi
done

IParray=()

###############################################################
# 解除登録されているがIPTABLESに登録されていないIPを一括登録
###############################################################

ATarray=()

for N in `atq | awk '{print $1}'`
do
    IPADDR=`at -c ${N} | grep iptables | cut -d " " -f 5`
    RuleCheck=`iptables -w -L INPUT -n | grep " $IPADDR "`
    sleepenh 0.4 > /dev/null 2>&1

    if [ -z "$RuleCheck" -a -n "$IPADDR" ]; then
        echo `iptables -w -I INPUT -s $IPADDR -j DROP > /dev/null 2>&1`
    fi

    ATarray=($IPADDR "${ATarray[@]}")
done

####################################################################
# IPTABLESに登録されているが、解除予約されていないIPをatに一括登録
####################################################################

for M in `iptables -w -L INPUT -n | grep DROP.*all | awk '{print $4}'`
do
    IPADDR=${M}

    if [ $IPADDR != '0.0.0.0/0' ]; then
        if ! `echo ${ATarray[@]} | grep -q "$IPADDR" > /dev/null 2>&1` ; then
            echo "iptables -D INPUT -s $IPADDR -j DROP > /dev/null 2>&1; \
            rm -f /var/log/swatch/$IPADDR > /dev/null 2>&1" | \
            at now+36hour > /dev/null 2>&1
       echo "`date` $IPADDR locked. Release schedule at `date --date '36hours' +'%Y/%m/%d %H:%M:%S'`." > /var/log/swatch/swatch.log
        fi
    fi
done
```

　<a href=https://qiita.com/yukisku/items/6efcee95186883ce6acb>前回の投稿記事</a>で作成した`/etc/cron.weekly/dl_iplist`中のコメントを外してIPアドレス一覧更新後の実行を有効化する。`iptables.sh`が実行されるとルールが再設定され、`swatch`で登録したIPが全て消えてしまう。作成した `atcheck.sh`は`crontab`で適当な間隔で実行（4-8時間間隔くらい）。

　実行権限を与える。

```bash
chmod 655 atcheck.sh
````

# swatchの起動とステータス確認

　swatchを起動。

```bash
systemctl start swatch
```

　自動起動を設定。

```bash
systemctl enable swatch
```

　起動状態を確認。

```bash
systemctl status swatch
```

```bash
● swatch.service - Swatchdog Log Monitering Daemon
     Loaded: loaded (/etc/systemd/system/swatch.service; disabled; vendor preset: enabled)
     Active: active (exited) since Sat 2021-03-30 18:11:28 JST; 1 day 19h ago
    Process: 962666 ExecStart=/usr/local/bin/swatch start (code=exited, status=0/SUCCESS)
   Main PID: 962666 (code=exited, status=0/SUCCESS)
      Tasks: 0 (limit: 18949)
     Memory: 6.0M
     CGroup: /system.slice/swatch.service

 3月 30 18:11:28 mydomain.jp systemd[1]: Starting Swatchdog Log Monitering Daemon...
 3月 30 18:11:28 mydomain.jp swatch[962666]:   Swatchdog Daemon has started.
 3月 30 18:11:28 mydomain.jp systemd[1]: Finished Swatchdog Log Monitering Daemon.
```

　こんな感じで表示されれば OK。以下コマンドでも pid と設定ファイル等が確認できる。

```bash
swatch status
```

　`Permission denied`と表示される場合、実行権限付与忘れを確認する。

# 管理のための便利スクリプト
　スクリプトが期待してない動作をして`iptables`に登録され、該当するIPを削除したいときなど、`AT`のジョブナンバーが知りたいことがしばしばある。`atq`コマンドで内容を見れるが、いちいち確認していくのが面倒。というわけで、ジョブナンバー、削除予定のIPアドレスを一覧で画面に出力するコマンドを自作した。

```bash
vi atqipshow.sh
```

```bash:atqipshow.sh
#!/bin/bash

PATH=/bin:/usr/sbin:/usr/bin
LANG=C

echo ""
echo ""
echo "------------- ATQIPSHOW ver 0.4b by yukisku begin. -------------"
echo ""
echo -e "  ATjob  \t IP addr     \t\t status"
echo "-----------------------------------------------------------------"

IParray=()
cnt=0
jobnumcnt=()

for N in `atq | sort -r | awk '{print $1}'`
do
    IPADDR=`at -c ${N} | grep iptables | cut -d " " -f 6`
    if [[ -z "$IPADDR" ]]; then
        IPADDR="Unrelated process"
    else
        jobnumcnt=$(( jobnumcnt + 1 ))
    fi

    Ovl=`echo "${IParray[@]}" | grep " $IPADDR "`
    if [[ -n "$Ovl" ]]; then
        cnt=$(( cnt + 1 ))
        echo -e "  $N \t\t $IPADDR    \t already registered"
    else
        IParray+=" $IPADDR "
        echo -e "  $N \t\t $IPADDR"
    fi
done
echo "-----------------------------------------------------------------"
echo ""

if [[ "$jobnumcnt" -ge 1 ]]; then
    echo " $jobnumcnt job(s) exist."
else
    echo " There is no job now."
fi

if [[ "$cnt" -ge 1 ]]; then
    echo " $cnt ATQ job(s) are overlapped (Will be deleted by atcheck)."
else
    echo " There is no job overlapped."
fi

echo ""
echo "-------------- ATQIPSHOW END --------------"
echo ""
```

使い方はbashで実行するだけ。

```bash
bash atqipshow.sh
```


ジョブナンバー、IPアドレス、重複登録されているかどうかが表示されます。



https://qiita.com/yukisku/items/18abab542bab159204bb