背景
前回、動的フィルタで捌こうとして(当然)失敗したのでリベンジ。
あれから結局、80番ポートを開けっ放しにして運用してたけど、不正アクセスを試みる通信が結構多くて精神的にあまりよろしくない。 普段は80番ポートは閉じておき、証明書の自動更新をするときだけ開くようにしたい。
概要
certbotから証明書更新用サーバーへの通信をRTX830上で走らせたluaスクリプトで監視。通信を検出したら80番ポートへの通信を開けるルールを投入する。 しばらく待って今度は80番ポートへの通信を遮断するルールを投入する。
前提
$ sudo certbot renew --dry-run
が成功することを事前に確認しておく。
手順
証明書を更新する際に、certbotが最初に通信するサーバーを調べる。以下の2つが多分それ。2つ目の方はおそらくテスト用。後でテストするので両方対象にする。
acme-v02.api.letsencrypt.org
acme-staging-v02.api.letsencrypt.org
RTX830のDNS
RTX830のフィルタ設定でFQDNを指定するためには、RTX830に対応するDNSキャッシュを持たせる必要がある。 そのために、自宅サーバーからRTX830に対して定期的にdigで問い合わせをする。自宅サーバーのsystemd timerを使ってサクッと登録。 なお、この手順は自宅サーバーが参照するDNSサーバーとしてRTX830を指定している場合には不要。
$ mkdir -p /home/pi/dig
$ touch /home/pi/dig/dig-bach
$ vim /home/pi/dig/dig-bach
acme-v02.api.letsencrypt.org
acme-staging-v02.api.letsencrypt.org
$ cd /home/pi/.config/systemd/user/
$ touch acme-dig.timer
$ touch acme-dig.service
[Unit]
Description="timer for acme dns request"
[Timer]
OnCalendar=*:0/1
[Install]
WantedBy=timers.target
[Unit]
Description="dig for acme"
[Service]
ExecStart=/usr/bin/dig -f /home/pi/dig/dig-batch @(RTX830のIPアドレス) &> /dev/null
Restart=on-failure
[Install]
WantedBy=default.target
$ systemctl --user daemon-reload
$ systemctl --user enable acme-dig.timer
$ systemctl --user list-timers --all
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sun 2025-03-16 11:05:00 JST 53s left Sun 2025-03-16 11:04:04 JST 1s ago acme-dig.timer acme-dig.service
自宅サーバーからログアウト。RTX830にログイン。DNSキャッシュにエントリがあるかを確認
> show dns cache | grep acme
Searching ...
A IN 56/240 acme-staging-v02.api.letsencrypt.org (172.65.46.172)
A IN 236/240 acme-v02.api.letsencrypt.org (172.65.32.248)
RTX830側でのルール設定
lan2のout側にcertbotからの通信をキャッチするためのルールを登録する。 後々わかりにくくならないように、in側には80番ポートへの通信を遮断するルールをあらかじめ入れておく。
ip lan2 secure filter out 11 20 21 31 22 81 82 98 dynamic 80 81 82 83 84 98 99
ip filter 81 pass-log 自宅サーバーのIPアドレス acme-staging-v02.api.letsencrypt.org,acme-v02.api.letsencrypt.org
ip lan2 secure filter in 10 20 21 30 90 91 92 99 dynamic 90 91 92
ip filter 92 reject * 自宅サーバーのIPアドレス tcpsyn * www
これで自宅サーバーから証明書更新用サーバーに通信が飛ぶと、filter 81でパケットを通過させたログがRTX830のsyslogに出るのでそれをluaスクリプトで拾ってfilter 92のrejectをpass-logに書き換える。ある程度時間がたったらrejectに戻す。
luaスクリプトの登録
さくっと書いてRTX830に転送、RTX830上で走らせたら完了。
--------------------------## 設定値 ##--------------------------------
-- 検出したい SYSLOG の文字列パターン。"()”は要エスケープ
ptn = "Passed at OUT%(81%)"
-- メールの設定(これは適宜)
mail_tbl = {
smtp_address = "環境に合わせて記載",
smtps = true,
smtp_auth_name = "環境に合わせて記載",
smtp_auth_password = "環境に合わせて記載",
smtp_auth_protocol = "環境に合わせて記載",
from = "環境に合わせて記載",
to = "環境に合わせて記載"
}
-- メールの送信に失敗した時に出力する SYSLOG のレベル (info, debug, notice)
log_level = "notice"
----------------------## 設定値ここまで ##----------------------------
------------------------------------------------------------
-- コマンドの実行結果を出力する関数 --
------------------------------------------------------------
function exec_command(cmd)
local rtn, str
rtn, str = rt.command(cmd)
if (not rtn) or (not str) then
str = "execution failure\r\n"
end
return rtn, string.format("# %s\r\n%s\r\n", cmd, str)
end
------------------------------------------------------------
-- 現在の日時を取得する関数 --
------------------------------------------------------------
function time_stamp()
local t
t = os.date("*t")
return string.format("%d/%02d/%02d %02d:%02d:%02d",t.year, t.month, t.day, t.hour, t.min, t.sec)
end
------------------------------------------------------------
-- メインルーチン --
------------------------------------------------------------
local rtn, str
while (true) do
rtn, str = rt.syslogwatch(ptn) -- SYSLOG の監視(SYSLOG の出力が行われない間、呼び出し元の Lua タスクはスリープ状態になる)。
if (rtn) and (str) then
rtn, str = rt.command("ip filter 92 pass-log * 自宅サーバーのIPアドレス tcpsyn * www")
rt.sleep(20)
rtn, str = rt.command("ip filter 92 reject * 自宅サーバーのIPアドレス tcpsyn * www")
mail_tbl.text = string.format("Detect search string. \r\n search string: \"%s\"\r\n\r\n", ptn)
-- mail_tbl.text = mail_tbl.text .. str[1]
mail_tbl.subject = string.format("pattern string detected (%s)", time_stamp())
rtn = rt.mail(mail_tbl)
rt.syslog(log_level, "executed lets_encrypt.lua")
if (not rtn) then
rt.syslog(log_level, "failed to send mail. lets_encrypt.lua")
end
end
end
以上
参考