0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ネットワークセキュリティ】BUFFALO WSR-1500AX2L×Raspberry Pi 4×Dockerで作る機械学習ログ監視 ~Syslog収集・Python解析・異常検知・メール通知~

0
Last updated at Posted at 2026-10-18

はじめに

家庭用ルーターのログには、機器の接続や切断、IPアドレスの割当て、設定変更など、ネットワークの状態を確認する手掛かりが記録されます。一方、管理画面を必要なときだけ開く方法では、変化を見逃したり、確認したい期間のログが残っていなかったりすることがあります。

本記事では、BUFFALO WSR-1500AX2LのSyslogをRaspberry Pi 4へ収集し、Pythonで解析してGmailへ通知する構成を、新規構築の順番で説明します。常時の受信・監視・通知はPi、モデルの学習・評価・配布はPCのDockerコンテナへ分担します。

ログの確認には、固定ルール・過去の件数との統計比較・Isolation Forestによる傾向判定を組み合わせます。学習側ではLoglizerによる数値データへの変換を利用し、実ログが必要量に達した場合に候補モデルを作り直します。評価に合格したモデルだけを自動転送し、Pi側でもモデルの判定動作テストを通してから切り替えます。

導入直後はモデルがなくても動作します。 まずルール監視と解析用データの蓄積を始め、統計・機械学習の結果はメールを増やさず記録・週報にまとめます。Pi自身の認証ログと実行中プログラムの実行ファイルも確認し、「不審な動作の兆候」と「ウイルス検査での検出」を別の件名でGmailへ通知します。 定期ファイル検査にはClamAVを使用します。

ログの件数や機械学習スコアは、侵入・感染・被害の確定情報ではありません。本構成は家庭内の全通信を取得する仕組みでも、Windows PCのウイルス検査でもありません。要確認の事象を記録し、人が元のログと照合できるようにする仕組みです。

この記事でわかること

  • Raspberry Pi 4の確認、64-bit OSとSSHの準備
  • BUFFALOのSyslog転送と、Piでの受信・保存
  • 何を・どこで・いつチェックし、どの条件で通知するか
  • ルール判定・統計判定・機械学習の違いと役割
  • Loglizerによる数値データへの変換とIsolation Forestの学習・評価
  • データ不足時の継続動作、合格モデルの自動配布、更新前モデルへの復元
  • Piの認証ログ・実行ファイルの確認と、ClamAVによる定期ファイル検査
  • 「不審な動作の兆候」「ウイルス検査での検出」を区別したGmail通知・週次報告
  • PC停止時も継続する処理と、PC起動が必要な処理

本記事は、完成構成を最初から準備するための手順書です。個人のメールアドレスやパスワードは掲載せず、例の値を使います。

作業場所の見方

表記 実施場所・役割
Windows PC/PowerShell Docker操作、SSH/SCP。Windows側のPythonは起動しない
Windows PC/Docker raspberrypi4-secure 内の開発・テスト・解析・学習
Raspberry Pi 4/SSH Pi本体、またはSSH接続先のLinux端末
BUFFALOルーター/管理画面 Syslog転送先・ログ項目の確認
Googleアカウント/ブラウザ 送信元アカウントのアプリパスワード作成
スマートフォン・PC Gmailの受信確認と内容の判断
解説のみ コマンドの実行なし

ssh pi@192.168.11.11 の後はPi側です。exit でWindows PowerShellへ戻ります。各コードブロックの実施場所を確認してください。

1. 完成するもの・前提・進め方

Piはルーターのログを受信・保存し、5分間ごとのログ集計・判定を行います。

例えば、10:00〜10:05、10:05〜10:10のように、時刻で区切った5分間のログをまとめて数えます。本記事では、このまとまりを「集計区間」と呼びます。ログは約5秒ごとに読み取り、10:00〜10:05の分は通常10:05:30以降に判定します。区間の終了後に30秒待つのは、遅れて届くログを考慮するためです。ログに未読分が残っている場合などは、判定がさらに遅れることがあります。受信経路の基本チェックとPiの認証ログ・実行ファイルの確認は、それぞれ約1分ごとです。ルーターへの疎通・サービス状態などの追加確認は約5分ごとです。学習用コンテナは毎週月曜2時、週報はPiから月曜9時に実行します。時刻はすべて日本時間です。

「5分ごとにメールを送る」構成ではありません。 ルールに該当するログ、状態チェックの異常、ファイル検査の検出・未完了などを、それぞれの条件で通知します。平常の状況と統計・機械学習の傾向変化は週報へまとめます。

必要なものはPi 4、有線LAN、適切なUSB-C電源、microSD、管理許可のあるWSR-1500AX2L、Windows PC、Docker Desktop、SSH/SCPです。Piのsudo権限とルーター管理権限を使用します。

項目 本手順の例 変更時に合わせる場所
Pi 192.168.11.11 SSH/SCP、rsyslog待受、ルーター転送先、コンテナ接続先
BUFFALO 192.168.11.1 rsyslog送信元条件、監視設定
外側のルーター 192.168.0.1 状態確認用gateway_ip。存在しなければ自環境の確認対象を指定
SSHユーザー pi SSH/SCP、配布スクリプト・受信フォルダーの所有者
Gmail YOUR_ADDRESS@gmail.com 送信元・通知先を自分のアドレスに変更
Syslog受信設定 30-buffalo.conf 同じUDP受信設定を別名で重複配置しない

1-1. 使用するプログラムと役割

router-monitor は、このプロジェクトのPython監視プログラムを常時起動するsystemdサービス名です。 ダウンロードしてインストールする同名の既製品ではありません。

名称 種類 役割
rsyslog OSのログ受信サービス ルーターのSyslogをファイルへ保存
router-monitor Piの独自systemdサービス ログ分類・ルール判定・統計判定・学習済みモデルでの判定・状態保存
security-status Piの独自systemdサービス 定期状態確認、Piの認証ログ・実行ファイル確認、ClamAV結果確認、種類別Gmail通知・週報
pi_behavior.py 本プロジェクトのPythonモジュール security-statusから呼び出す認証ログ・実行ファイルの確認処理。別の常駐サービスは作らない
router_monitor Pythonパッケージ 監視・解析用の数値データ・学習・配布のソース
routermon Piの専用ユーザー 日常のルーターログ監視を限定した権限で実行
Loglizer ログ解析用のツール群 学習に使うイベント件数を数値データへ変換
Isolation Forest 異常傾向を検出する学習方式 件数・割合などの組合せが普段と異なるか評価
ClamAV ファイル検査ツール Piの対象フォルダーを定期検査

Loglizer自体がBUFFALO向けの学習済みモデルというわけではありません。実装では公式の preprocessing.FeatureExtractor は取得時点の版を固定して同梱し、その出力とPi側で計算した件数が一致することを検査します。Loglizer公式リポジトリ

1-2. どこで何を動かすのか

図1:Piで常時動く収集・解析・通知

ChatGPT 画像 2026年10月21日 11_33_34.png

ルール通知は router-monitor、状態異常・Piの不審動作の兆候・ファイル検査・週報は security-status が、それぞれのSQLite送信キューからGmailへ送ります。

PCを停止してもPi側の処理は続きます。 Gmail送信にはPiのインターネット接続が必要です。Pi自体・監視プロセス・メール経路が停止した場合は、このPiから通知できません。

図2:コンテナで行う週次学習・評価・自動配布

ChatGPT 画像 2026年10月21日 11_35_55.png

予定時刻にPCが停止していた場合、次回コンテナ起動後に最新の未実行枠を1回確認します。直前の失敗による再試行待ちがあれば、その待ち時間も適用されます。停止期間のすべての週をまとめて学習する方式ではありません。

1-3. 使用するイメージとコンテナ

名前 種類・役割
raspberrypi4-secure-trainer:ml-20261021 Python 3.13をベースに、学習・SSH連携の依存ライブラリとソースを収録するDockerイメージ
raspberrypi4-secure 上記イメージから起動する開発・テスト・学習・評価・配布用コンテナ
Raspberry Pi 4 ログ受信・監視・学習済みモデルによる判定・通知を動かす実機

Dockerイメージは実行環境のひな形、コンテナはそのイメージから起動した実行環境です。本記事では、1つのイメージから raspberrypi4-secure を起動します。

1-4. プロジェクト構成図①:Windows PCとDocker

Windows PC
  C:\work\RaspberryPi4-Secure
      |
      | /workspaceへ共有
      v
  コンテナ: raspberrypi4-secure
    |
    |-- router_monitor/core.py       共通の分類・解析用の数値データ
    |-- router_monitor/vendor/loglizer/ 公式の解析に使う数値データへの変換処理
    |-- router_monitor/train_pipeline.py 学習・判定基準の調整・評価
    |-- router_monitor/pipeline.py       予定確認・SSH取得・合格時の転送
    |-- tests/                      テスト
    `-- state/ml-pipeline/           取得した集計データの控え・評価・予定状態

  専用Docker volume(ソースと分離)
    raspberrypi4-secure-ml-secret
      `-- pi-password               SSHパスワード / 0600

  公開ポートなし / Dockerソケット共有なし
  PiとのSSH連携にbridgeネットワークを使用

/workspace はPCのプロジェクトを共有する作業場所です。イメージ内の /app にもソースを収録します。SSH資格情報は専用volumeへ分離し、イメージに含めません。

1-5. プロジェクト構成図②:Raspberry Pi 4

Raspberry Pi 4
  |
  |-- /etc/rsyslog.d/30-buffalo.conf  ログ受信設定
  |-- /var/log/buffalo.log           受信した生ログ
  |
  |-- /opt/router-monitor/
  |     |-- router_monitor/          監視・PCとPiで共通の数値データ・JSONモデルによる判定
  |     |-- .venv/                   監視用Python仮想環境
  |     `-- models/
  |           |-- current.json       採用モデル / 初期は存在しない
  |           `-- previous.json      以前のモデル / 更新後に生成
  |
  |-- /var/lib/router-monitor/monitor.db
  |     |-- windows                  既存ルール用の集計
  |     |-- analytics_windows        新しい解析用の数値データと学習候補の条件
  |     |-- analytics_predictions    統計・ML結果
  |     `-- kv / outbox              処理位置・状態・ルール通知キュー
  |
  |-- /opt/security-status/          状態確認・ClamAV実行/結果確認
  |-- /var/lib/security-status/      状態と週報・要確認メールキュー
  |-- /var/lib/pi-antivirus/         検査・定義更新の結果
  |-- /var/log/pi-antivirus/         非公開の検査ログ
  |
  `-- /var/lib/router-ml/
        |-- inbox/                  コンテナから受け取る候補と状態
        `-- pipeline-status.json    学習処理の実行結果

/opt/security-status/pi_behavior.py が認証ログと実行ファイルを確認します。結果・重複通知を抑える状態・通知キューは /var/lib/security-status/status.db に保存し、PCへ生の認証ログを送る必要はありません。

SQLiteは独立したDBサーバーではなく、PythonがローカルDBファイルを読み書きする方式です。ログ本文と、集計・判定・処理状態を分けて保存します。

1-6. 必要なソース一式

記事本文だけではPythonソース一式はそろいません。 以下の対応版を同じプロジェクトから用意し、C:\work\RaspberryPi4-Secure へ配置してください。config.pipeline.json の接続先とホスト指紋は環境に合わせて変更します。

RaspberryPi4-Secure/
  router_monitor/
    core.py / monitor.py / health.py / notifications.py
    analytics.py / portable_forest.py / ml_admin.py
    train_pipeline.py / pipeline.py
    vendor/loglizer/preprocessing.py / LICENSE / SOURCE.json
  security_monitor.py / antivirus_status.py / pi_antivirus.py
  windows_collector.py / docker_collector.py   # 互換テストで参照。Windows収集は起動しない
  tests/
  pi/
    30-buffalo.conf.template / router-monitor.service
    security-status.service / buffalo.logrotate
    pi-antivirus-*.service / pi-antivirus-*.timer
  scripts/
    router-ml-admin / make_demo.py
  docker/ml-runtime.Dockerfile
  requirements.txt / compose.dev.yaml / .dockerignore
  config.example.json / config.pipeline.json
  state/                         # 実行後に生成。Git・ビルド対象外

このソース一式に加え、pi_behavior.py・pi/security-status-behavior.conf・tests/test_pi_behavior.py を同じ対応版から用意します。認証・実行ファイル監視にはこれらも必要です。

実施場所:Windows PC/PowerShell

Set-Location C:\work\RaspberryPi4-Secure
Get-ChildItem router_monitor, tests, pi, scripts, docker
Get-Command ssh, scp, docker
docker version
docker compose version

PC側の掲載コマンドはPowerShell 7を想定しています。Docker DesktopはLinuxコンテナを利用でき、Serverへ接続できる状態にします。PC側のPythonはコンテナ内で実行し、WindowsタスクスケジューラからPythonを起動しません。

1-7. 全体の順序

  1. Pi 4・64-bit OS・SSHを準備する。
  2. 対応するソースを転送し、Syslogの受信とルーター転送を確認する。
  3. ルール監視と補助解析をdry-runで起動する。
  4. Gmailのテスト送信と受信を確認する。
  5. Dockerに学習・評価・自動配布の実行環境を用意し、コンテナでテストする。
  6. Piの状態確認・週報・モデル受け口・定期ファイル検査を設定し、コンテナからの実接続を確認する。
  7. 実ログを蓄積し、週次の学習条件確認を続ける。
  8. 合格モデルができたら自動配布し、記録と週報で結果を確認する。

2. Raspberry Pi 4の確認 【Raspberry Pi 4/SSH】

PiのターミナルまたはSSH接続先で実行します。

実施場所:Raspberry Pi 4/SSH

cat /proc/device-tree/model
uname -m
python3 --version
cat /etc/os-release

表示例は Raspberry Pi 4 Model B Rev 1.2 です。64-bit OSでは aarch64 を確認してください。モデル名の末尾に改行がなく、次のプロンプトが続いても異常ではありません。

実施場所:Raspberry Pi 4/SSH

grep '^Model' /proc/cpuinfo
grep '^Revision' /proc/cpuinfo

Model はモデル名、Revision はリビジョンコードです。pinout が導入済みなら、基板図とGPIO情報も表示できます。

3. 64-bit OSの準備

既に稼働しているOS環境が32-bit の場合、64-bit OSを新規に書き込みます。書込み先カードの内容は消えるため、必要なデータを退避し、対象カードを確認してください。

  1. PCでRaspberry Pi Imagerを開きます。
  2. デバイスにRaspberry Pi 4、OSにRaspberry Pi OS Lite(64-bit)を選びます。
  3. 書込み先のSDカードを確認します。対象カードの既存データは消去されます。
  4. 初期設定でユーザー・パスワード・SSHを設定します。本手順の例ではユーザー pi とパスワード認証を使用します。固有のパスワードを設定してください。
  5. 書込みと検証が終わったらカードを取り出し、Piへ挿して起動します。
  6. LANのIPを確認し、SSHで接続して前節のコマンドを実行します。

ImagerでOS一覧から選ぶ場合は、Imagerがイメージをダウンロードします。別途ダウンロードした .img.xz は不要です。手元のイメージを使う場合だけ、カスタムイメージを選びます。.img.xz をSDカードへ通常のファイルとしてコピーするだけでは起動用カードになりません。

再インストール後はSSHサーバーのホスト鍵が変わることがあります。Pi本体で次の指紋を確認し、SSH接続先の鍵と照合します。これは公開鍵の確認であり、新しいSSH認証鍵を作成する操作ではありません。

実施場所:Raspberry Pi 4/SSH

ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

4. ソースの転送(OS準備後)

ソースをPCからPiへ転送する

Windows PowerShellで実行します。初回SSH接続ではPi本体で確認したホスト鍵指紋を照合してください。ホスト鍵検証を無効にするオプションは使用しません。

実施場所:Windows PC/PowerShell

Set-Location C:\work\RaspberryPi4-Secure
ssh pi@192.168.11.11

SSHでPiに入ったら、転送先を作成していったん戻ります。

実施場所:Raspberry Pi 4/SSH

mkdir -p ~/router-monitor-bootstrap
exit

Windows PowerShellから転送します。要求されたパスワードはプロンプトに入力します。

実施場所:Windows PC/PowerShell

scp -r router_monitor tests pi scripts security_monitor.py pi_behavior.py antivirus_status.py pi_antivirus.py config.example.json requirements.txt pi@192.168.11.11:router-monitor-bootstrap/
ssh pi@192.168.11.11

pi は配備テンプレートを置いたローカルディレクトリ名でもあり、SSHのユーザー名でもあります。転送後のPiでは次で確認します。

実施場所:Raspberry Pi 4/SSH

cd ~/router-monitor-bootstrap
ls router_monitor tests pi
python3 -m compileall -q router_monitor

ここではソースの転送と構文確認まで行います。依存ライブラリが必要な学習・配布テストを、未準備のPiで一括実行しません。対応版の全テストは第11章でコンテナ内から実行します。

5. PiでSyslogを受信する 【Raspberry Pi 4/SSH】

以下は本手順のIP例での構成です。実際のアドレスに合わせて変更してください。

実施場所:Raspberry Pi 4/SSH

sudo apt update
sudo apt install -y rsyslog tcpdump python3-venv logrotate nano

専用ユーザー routermon を作成し、ログをroot所有・routermonグループ・0640で保存します。既存ユーザーがある場合は重複作成しません。

実施場所:Raspberry Pi 4/SSH

getent passwd routermon || sudo useradd --system --user-group --home-dir /opt/router-monitor --shell /usr/sbin/nologin routermon
id routermon

Piで既存の受信設定を調べます。

sudo grep -R -n -E 'imudp|port="514"' /etc/rsyslog.conf /etc/rsyslog.d
ps -o user,group,args -C rsyslogd

一致がなければgrepは何も表示せず終了コード1になります。設定例はrsyslogがrootで動く検証環境に合わせています。別のユーザーで稼働している環境では、そのまま所有権設定を流用しないでください。

既存のimudp設定との重複を確認したうえで、Piで sudo nano /etc/rsyslog.d/30-buffalo.conf を開き、次の内容を保存してください(Ctrl+O、Enter、Ctrl+X)。保存後に下の構文確認と再起動へ進みます。

設定名は /etc/rsyslog.d/30-buffalo.conf に統一します。30-router.conf などに既存の受信設定があれば、二重配置せず内容を確認してください。

module(load="imudp")
template(name="BuffaloLine" type="string" string="%timegenerated:::date-rfc3339% %fromhost-ip% %syslogtag%%msg%\n")
ruleset(name="buffaloRemote") {
  if ($fromhost-ip == "192.168.11.1") then {
    action(type="omfile" file="/var/log/buffalo.log" template="BuffaloLine"
           fileOwner="root" fileGroup="routermon" fileCreateMode="0640")
  }
  stop
}
input(type="imudp" port="514" address="192.168.11.11" ruleset="buffaloRemote")

受信時刻・送信元IP・メッセージを保存します。ルーター側のイベント発生時刻を推測して補いません。配布テンプレートでは送信元の ROUTER_IP を置換し、実機と同様に受信側の address も環境に合わせます。

sudo rsyslogd -N1
sudo systemctl enable --now rsyslog
sudo systemctl restart rsyslog
sudo ss -lunp

UDPには到達保証や送信元認証がありません。送信元IPの条件だけでは偽装を防げないため、管理LAN内で運用します。WAN側にUDP/514を公開する必要はありません。PiのIPが変わると転送先と待受設定がずれるため、DHCP予約などで固定する必要があります。

この時点では、転送がまだ無効ならログファイルが存在しないことがあります。次節でルーターを設定してから受信を確認します。

6. BUFFALOのログ転送設定 【BUFFALOルーター/管理画面・Raspberry Pi 4/SSH】

管理画面の詳細設定から「管理」→「ログ」へ進み、ログ情報転送を有効にし、転送先にPiのIPを指定します。標準ログ項目を確認し、まず通常のログを転送してください。詳細ログや防御設定は必要性を確認せず変更しないでください。

設定前にルーターの動作モードと元の転送設定を記録します。メニューが見当たらない場合は、機種・ファームウェア・モードを確認してください。

設定後、Piで次を実行します。

実施場所:Raspberry Pi 4/SSH

sudo tail -F /var/log/buffalo.log

終了はCtrl+Cです。新しいログが届くまで表示が増えない場合があります。ファイルに受信日時・ルーターIP・本文が記録されることが合格条件です。自然な管理操作などで通常イベントを確認し、攻撃通信を発生させる必要はありません。

ファイルに届かない場合はパケットとrsyslogを切り分けます。

実施場所:Raspberry Pi 4/SSH

sudo tcpdump -ni any 'src host 192.168.11.1 and udp dst port 514'

tcpdumpもCtrl+Cで終了します。パケットが届くのに保存されなければ構文・条件・権限・sudo journalctl -u rsyslog -n 30 --no-pager を調べます。パケットもなければ転送先・IP・LANを確認します。

標準ログが届いたことと、攻撃検知に十分なログが得られることは別です。 認証失敗や通信拒否の分類は、該当する実ログを確認したうえで調整します。

操作項目はファームウェアや動作モードで異なる場合があります。BUFFALO公式マニュアルと実際の画面を確認してください。

7. Pythonと監視サービスを配置する 【Raspberry Pi 4/SSH】

専用ディレクトリ・仮想環境・コードを配置する

以下は新規構築時にPiで実行する手順です。第5節で作成済みの routermon を使用します。

実施場所:Raspberry Pi 4/SSH

cd ~/router-monitor-bootstrap
sudo install -d -o root -g root -m 0755 /opt/router-monitor
sudo install -d -o root -g root -m 0755 /opt/router-monitor/router_monitor
sudo install -m 0644 router_monitor/*.py /opt/router-monitor/router_monitor/
sudo cp -R router_monitor/vendor /opt/router-monitor/router_monitor/
sudo python3 -m venv /opt/router-monitor/.venv
sudo install -d -o root -g routermon -m 0750 /etc/router-monitor

ルール監視のPython処理は標準ライブラリで動きます。受信状態診断ではOSのping・systemctl・ssも使うため、Piでコマンドを用意します。

sudo apt-get install -y iputils-ping iproute2
command -v ping systemctl ss

今回のPi向けJSONモデルによる判定と補助解析は標準ライブラリで動作します。Piで学習・依存付きテストを実行する場合を除き、学習済みモデルによる判定用にnumpy・pandas・scikit-learnを追加する必要はありません。学習用依存はPCコンテナへ配置します。

初期設定を作成する

次は新規構築専用です。現在稼働中の設定を上書きするためには使用しません。

sudo install -o root -g routermon -m 0640 config.example.json /etc/router-monitor/config.json
sudo python3 - <<'PY'
import json
from pathlib import Path
p = Path('/etc/router-monitor/config.json')
c = json.loads(p.read_text(encoding='utf-8-sig'))
c.update(
    log_path='/var/log/buffalo.log',
    state_path='/var/lib/router-monitor/monitor.db',
    router_ip='192.168.11.1',
    health_checks_enabled=True,
    health_notifications_enabled=False,
    analytics_enabled=True,
    analytics_model_path='/opt/router-monitor/models/current.json',
    analytics_retention_days=90,
    syslog_bind_ip='192.168.11.11',
    notification_mode='dry-run',
    model_path='',
    metadata_path='',
    model_sha256='',
)
p.write_text(json.dumps(c, indent=2) + '\n', encoding='utf-8')
PY

確認するのは設定ファイルの権限と必要項目です。認証ファイル smtp.env の内容は表示しません。

sudo stat -c '%a %U:%G %n' /etc/router-monitor/config.json

確認値は 640 root:routermon です。状態ディレクトリは次節のsystemdが作成します。モデル未配置でも起動します。モデルの指定には analytics_model_path を使用します。model_path・metadata_path・model_sha256 はこの構成では使用しないため空にします。

基本チェックの状態は保持し、個別の平常時未受信メールは抑制します。第12章で security-status を起動するまでは、追加の状態通知・週報はまだ動きません。

systemdを配置して起動する 【Raspberry Pi 4/SSH】

sudo install -o root -g root -m 0644 pi/router-monitor.service /etc/systemd/system/router-monitor.service
sudo systemd-analyze verify /etc/systemd/system/router-monitor.service
sudo systemctl daemon-reload
sudo systemctl enable --now router-monitor
sudo systemctl is-active router-monitor
sudo systemctl is-enabled router-monitor

確認値は active と enabled です。同梱するサービス設定は以下です。

[Unit]
Description=Router Security Monitor
After=network-online.target rsyslog.service
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=simple
User=routermon
Group=routermon
WorkingDirectory=/opt/router-monitor
EnvironmentFile=-/etc/router-monitor/smtp.env
ExecStart=/usr/bin/flock --no-fork --nonblock /var/lib/router-monitor/monitor.lock /opt/router-monitor/.venv/bin/python -m router_monitor.monitor --config /etc/router-monitor/config.json
Restart=on-failure
RestartSec=15
StateDirectory=router-monitor
StateDirectoryMode=0700
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/router-monitor

[Install]
WantedBy=multi-user.target

EnvironmentFile の先頭のマイナスにより、メール認証ファイル未作成でも初期dry-runは起動できます。SMTP通知へ切り替える前には認証ファイルが必要です。

配置先一覧 【解説のみ】

Pi上のパス 内容・権限
/etc/rsyslog.d/30-buffalo.conf 受信設定。root管理
/var/log/buffalo.log 受信ログ。root:routermon、0640
/opt/router-monitor/router_monitor/ Pythonコード。root管理
/opt/router-monitor/.venv/ Python仮想環境
/etc/router-monitor/config.json 監視設定。root:routermon、0640
/etc/router-monitor/smtp.env メール認証。root:root、0600
/etc/systemd/system/router-monitor.service 常時起動設定
/etc/logrotate.d/buffalo ローテーション設定
/var/lib/router-monitor/monitor.db 読込み位置・ルール用集計・補助解析用データ・判定・通知キュー
/opt/router-monitor/models/current.json 評価合格モデル。初期はなくてよい
/opt/router-monitor/models/previous.json 更新前のモデル。更新後に生成

8. 何を・どこで・どの条件でチェックするか

8-1. 確認項目と頻度の一覧

処理 実行場所 内容・役割 実行タイミング 結果・通知
実ログの受信 Pi / rsyslog ルーターから転送されたSyslogを保存 ログの到着時 ログファイルへ保存。すべての通信や家庭内機器の内部を検査する機能ではない
既存ルール判定 Pi / router-monitor 認証失敗相当・通信拒否相当の件数と設定基準を比較 5秒ごとにログを読み、5分間の集計区間が終わって30秒後以降に判定 閾値に該当すれば既存Gmail通知。同種の短時間の繰り返しは抑制。発生した瞬間の通知ではない
受信経路の基本確認 Pi / router-monitor ルーターping、rsyslog、UDP514、ログ読取り 約1分ごと 状態を記録。平常のログ未受信メールは抑制し週報へ集約
セキュリティ状態確認 Pi / security-status BUFFALO/BL900HWの疎通、監視サービス、受信口、ログ 約5分ごと。主要パッケージ整合性は1日ごと 基本状態の異常は2回連続で通知対象。同じ状態の重複を抑制
Piの認証ログ確認 Pi / security-status → pi_behavior.py SSH認証失敗関連ログ、sudo認証失敗、rootへのSSHログイン成功 約1分ごと。失敗件数は直近5分を確認 件数条件・rootログインに該当すれば「不審な動作の兆候」。ログを読めなければ「監視確認不能」
Piの実行ファイル確認 Pi / security-status → pi_behavior.py 全ユーザーのプロセスについて、一時フォルダー内・削除済みの実行ファイルか確認 約1分ごと 該当すれば「不審な動作の兆候」。正常な更新でも該当するため、人が確認
統計判定 Pi / 補助解析 各イベント件数が過去の中央値・ばらつきから大きく増えたか 5分間の集計が終わるごと。学習に使える集計区間が30区間以上必要 SQLiteへ記録し週報に掲載。単独の即時メールなし
学習済みモデルによる判定 Pi / 補助解析 複数の件数・割合・直前の集計区間との差の組合せが普段と違うか 集計が終わった区間ごと。評価合格モデルの配備後のみ 判定値・モデルID・判定結果を記録。単独の即時メールなし
学習・評価・更新 PCのraspberrypi4-secure Loglizerによる数値データへの変換とIsolation Forestの学習、品質検査、合格時のみ配布 毎週月曜02:00 JSTの枠を、正常稼働時は約5分間隔で確認。PC停止なら次回起動後 不足・不合格は見送り。現行モデルを維持。PC停止中の学習はできない
定義更新・Piファイル検査 Pi / ClamAV ウイルス定義更新と対象フォルダーのファイル検査 定義は毎日01:30、検査は日曜03:00 JST 検出・未完了・定義更新失敗を通知。削除・隔離なし。家庭内の他機器は検査しない
週次報告 Pi / security-status 基本状態・認証と実行ファイルの確認・解析状況・学習実行結果・Piファイル検査をまとめる 毎週月曜09:00 JST Gmailへ送信。未確認・データ不足を問題なしとして扱わない

以下の回数・閾値は本プロジェクトの設定です。すべての環境で最適と確認された値ではありません。

項目 設定・動作
ログ読取り 約5秒ごと
5分間ごとのログ集計・判定 受信時刻を基準に、10:00〜10:05などの5分間で区切る
ルール判定開始 集計区間の終了後に30秒の猶予。未読のログがあれば確定を待つ
認証失敗相当 5分間のログで10件以上
通信拒否相当 5分間のログで100件以上
ルール通知抑制 anomaly種別で30分。別の理由でも抑制される場合がある
受信経路の基本確認 約1分ごと。状態は保存し、個別のログ未受信メールは無効
追加の状態確認 約5分ごと。基本状態の異常は2回連続で通知対象
PiのSSH認証失敗関連ログ 直近5分に10件以上で「不審な動作の兆候」。2回連続の条件は使わない
Piのsudo認証失敗 直近5分に5件以上で「不審な動作の兆候」
rootへのSSHログイン 成功記録を取得したら要確認。通常ユーザーの成功記録は週報用に数える
実行ファイルの確認 /tmp/・/var/tmp/・/dev/shm/ 配下、または削除済みファイルから動くプロセスを要確認
主要パッケージ整合性 約1日ごと。設定以外の変更・確認不能を通知条件に使う

「危険が発生した瞬間を必ず検知して即時通知する」仕組みではありません。 ルールは区間の集計が確定した後、状態確認は検査間隔と連続条件を満たした後、ファイル検査は定期検査で該当ファイルを調べた後に通知処理へ進みます。認証・実行ファイルの兆候は約1分ごとの確認後に通知キューへ入ります。ClamAVの結果は約30秒ごとに確認します。メール経路の遅延・再送も別途あります。

8-2. ルール・統計・機械学習の違い

機械学習には、ログから数えた件数・割合・前の区間との差などの数値を渡します。これらは技術的には「特徴量」と呼ばれます。また、学習済みモデルを使って新しいデータを判定する処理は「推論」と呼ばれます。本記事では、処理内容が分かる日本語で説明します。

方式 判定すること 学習・データの条件 初期の通知
ルール 認証失敗・拒否相当の件数が設定値以上か 機械学習モデルは不要 条件該当時にメール
統計 過去の通常時の件数と比べ、大きく増えたか 学習に使える集計区間が30区間以上 記録と週報
Isolation Forest 複数項目の件数・割合の組合せが普段と異なるか 実ログの学習・評価に合格したモデル 記録と週報

統計は直前28日の学習に使える集計区間を最大200区間参照し、過去の中心的な件数と、ばらつきの大きさを使います。具体的には、件数を順に並べた中央の値である「中央値」と、各値が中央値からどの程度離れるかを表す「MAD」を用います。判定基準は max(10, 中央値 + 6 × max(1, 1.4826 × MAD)) とし、総件数と各イベント件数の増加を記録します。

Isolation Forestは、ランダムな分割によって孤立しやすい数値データを異常候補とする方式です。ログ文章の意味や攻撃手順を理解するモデルではありません。本構成ではscikit-learnの実装を学習に使用し、木構造をJSONへ出力します。Isolation Forest公式説明

8-3. ログから何を数えるか

新しい補助解析では、ログを次の8種類に排他的に分類します。1行は最初に一致した1種類へ数え、どれにも一致しなければ「その他」です。

分類 表現・ログの例 読み方
認証失敗 login failed、authentication failed、認証失敗 誤入力・設定不一致でも発生し得る
拒否 deny、drop、拒否 拒否・遮断した記録であり、侵入成功の証拠ではない
切断 deauthenticated、disassociated、disconnected 通常の端末切断・非使用時の切断も含む
接続 associated、connected 正常な接続で増えることがある
DHCP DHCPS、DHCPC、DHCP IPアドレスの割当て・更新など
システム SYSTEM、reboot、restart 再起動等の動作確認に使う
設定変更 CONFIGURE、configuration change 管理操作に心当たりがあるか確認
その他 上記に当てはまらない行 意味を自動で推測しない

この分類は core.py の定義に従います。例えばSYSTEMタグ付きでも、先にDHCPのパターンに一致すればDHCPへ分類されます。実機ではDHCP・hostapdの切断・CONFIGURE・SYSTEMの形式を確認しましたが、すべての異常表現を実機で検出済みという意味ではありません。

機械学習へ渡す数値データは、次の項目です。

  • ログの種類ごとの件数と、総件数
  • 8種類のログがそれぞれ全体に占める割合
  • 学習時には現れなかった分類の件数
  • 直前の5分間と時間が連続している場合の件数差
  • 直前の集計区間が存在するかどうか

Loglizerで作ったイベント件数と、Pi側の共通処理の件数を照合します。

ログが来ていない時間を、正常なゼロ件データとして埋めません。 時間が連続していない集計区間どうしの件数差も使いません。直前の集計区間が存在するかどうかは、別の値で記録します。ルール用の4項目の集計と、補助解析用の数値データは目的ごとに異なるデータ項目として、別のテーブルへ保存します。

8-4. 学習に使うログと除外するログ

受信時に直近180秒以内の基本チェックが正常であり、既存ルールの認証・拒否閾値に該当しない集計区間を学習候補にします。受信時の基本チェックを伴わない再集計した区間は、その時点の経路状態を確認できないため、分析用に保存しても学習候補から除きます。

候補判定は集計区間内の各イベント受信時の状態を使います。後からチェックが正常化しても、その集計区間全体を自動的に候補へ戻すわけではありません。この条件で候補になっても、無害と確定したデータではありません。ping応答やUDP待受だけでパケット欠落・送信停止は除外できず、未知の異常をすべて排除できるわけでもありません。調査時は元のログと分類・件数を確認してください。

テスト用の模擬ログは、プログラムで作る架空のログです。「合成ログ」とも呼ばれますが、本記事では「模擬ログ」と表記します。読取り・集計・モデルによる判定結果の一致を試験するために使い、実ログの学習候補や本番モデルへ混ぜません。

8-5. 学習・更新・採用の条件

条件 本実装の基準
データ量 学習に使える集計区間が300区間以上
期間・日数 最初と最後の集計区間の間に7日以上の幅、5日以上の観測日。観測日はUTC日付基準で数える
時系列分割 古い順に学習・判定基準の調整・最終評価。それぞれ最低200区間・50区間・50区間
特徴の変動 2項目以上に変動がある
更新時の新規量 前回のモデルで使用した最後の時刻(data_end)より後に、学習に使える集計区間が50区間以上
学習量の上限 学習に使える集計区間の直近20,000区間を使用し、学習区間の総イベント数が2,000,000を超える場合は見送る
学習設定 100本、1本あたり最大256件(学習に使う集計区間が少なければその件数)、random_state=42、n_jobs=1
通常と異常候補を分ける基準 判定基準を調整するデータの普段と違う程度を示す数値を小さい順に並べ、約99%の位置の値を使う
最終評価 傾向変化率5%以下、現行モデルより増加2ポイント以下
参考の極端値テスト 認証・拒否・切断を大きくした3ケース中2ケース以上に反応
Piとの数値一致 scikit-learnとJSONモデルによる判定の差が0.0000001未満
配布後 Piが形式・記録された評価合格条件・最小データ数・モデルの判定動作テストを確認

これらは自動配布のための運用上の基準です。傾向変化率は誤検知率ではなく、極端値テストの成功も実攻撃の検出率を示しません。 検出性能を測るには、実際の事象に対する正解情報が必要です。

Isolation Forestは、ログ1行を受信するたびにモデルを書き換える方式ではありません。蓄積データから週次で候補を作り直し、評価後に切り替えます。分類用の正規表現や既存ルール閾値は自動変更しません。

8-6. ログ未受信と受信経路の異常を分ける

正常なWi-Fi接続・切断、DHCP更新でもログは出ます。一方、接続が安定していれば長時間出ない場合があります。通常のWeb閲覧をすべて記録しているわけではありません。

基本チェック 確認すること
ルーターping 応答があるか。無応答でもICMP制限の可能性はある
rsyslog サービスがactiveか
UDP 514 設定した受信IP等で待受中か
ログ読取り 監視ユーザーがログを開けるか

基本チェックは約1分ごとに保存します。ログ未受信30分などの内部状態判定は残しますが、health_notifications_enabled=false にして、「ログ未受信・基本チェック正常」等の個別メールは送らない運用にします。追加の状態チェックと週報は security-status が担当します。

基本チェック正常は、ログがルーターから送られている証明ではありません。 気になる場合は、第6章のパケット観測、管理画面の最新ログとPiの最終受信を照合してください。

実施場所:Raspberry Pi 4/SSH(診断のみ・メールなし)

cd /opt/router-monitor
sudo -u routermon .venv/bin/python - <<'PY'
import json
from pathlib import Path
from router_monitor.health import check_health
c = json.loads(Path('/etc/router-monitor/config.json').read_text())
print(check_health(c))
PY

ip -brief address
ping -c 3 -W 2 192.168.11.1
systemctl is-active rsyslog router-monitor
sudo ss -ulnp 'sport = :514'
sudo rsyslogd -N1
sudo stat -c 'modified=%y bytes=%s' /var/log/buffalo.log

診断結果の True はチェック正常、False は異常、None は診断不能です。ルーターのログ転送設定は自動取得していません。ルーター内に新しいイベントがあるのにPiへ届いていなければ、転送先・LAN・送信元条件・権限を調べます。

9. 再起動・ログローテーション・送信失敗への対応 【Raspberry Pi 4/SSH】

読込み位置・集計・送信キューをSQLiteへ保存します。診断状態も保存し、再起動前後で監視状態を追跡します。再開時に同じログを最初から処理しないためです。rename/create型のローテーションを前提とします。サービス再起動と本体再起動は、別の試験として記録します。

ログ設定は日次、30世代、maxsize 20M、圧縮とdelaycompressです。サイズ条件はlogrotate実行時に判定されるため、20MBが厳密な上限になるわけではありません。ルール用の集計が終わった区間は30日、補助解析の集計区間と判定結果は90日を保持基準とします。生ログのローテーションとは別の保存条件です。

SMTP失敗時はキューから再試行します。送信成功直後・DB更新前にプロセスが停止すると重複送信が起きる余地があります。dry-runで記録した古い通知が後から実送信されないよう分離しています。複数回のローテーションをまたぐ長期停止については、手動復旧が必要になる場合があります。

ローテーションの配置と実機試験

Piの作業ディレクトリへ戻って実行します。

cd ~/router-monitor-bootstrap
sudo install -o root -g root -m 0644 pi/buffalo.logrotate /etc/logrotate.d/buffalo
sudo logrotate --debug /etc/logrotate.d/buffalo

配置内容は次のとおりです。

/var/log/buffalo.log {
    daily
    rotate 30
    maxsize 20M
    missingok
    notifempty
    compress
    delaycompress
    create 0640 root routermon
    sharedscripts
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}

以下は切替試験を行う場合の操作です。ローテーション前のログを保持し、新しいログへ追従するか確認します。強制ローテーションは毎回の状態確認に使うコマンドではありません。

sudo systemctl restart router-monitor
sudo logrotate --force /etc/logrotate.d/buffalo
sudo ls -li /var/log/buffalo.log*
sudo systemctl is-active router-monitor rsyslog

DBを読取り専用で開き、対象ファイルと位置を確認できます。

sudo -u routermon /opt/router-monitor/.venv/bin/python - <<'PY'
import sqlite3
c = sqlite3.connect('file:/var/lib/router-monitor/monitor.db?mode=ro', uri=True)
print(c.execute("SELECT key,value FROM kv WHERE key IN ('file_id','offset')").fetchall())
c.close()
PY

切替直後にoffsetが0でも、その後ログが来れば増えます。file_idのinode部分をls -liの値と比較し、監視が新ファイルを読んでいることを確認します。

10. Gmail通知の設定と実送信確認 【Googleアカウント/ブラウザ・Raspberry Pi 4/SSH】

送信元と通知先を決めてください。同一でも別アドレスでも構いません。アプリパスワードは送信元アカウントで作成します。通知先だけ変更する場合はsmtp_toを更新してサービスを再起動します。APIやアプリパスワードの再取得は不要です。送信元を変える場合は、その送信元の認証情報が必要です。

Raspberry Pi 4/設定ファイル(抜粋)

{
  "smtp_host": "smtp.gmail.com",
  "smtp_port": 587,
  "smtp_security": "starttls",
  "smtp_from": "YOUR_ADDRESS@gmail.com",
  "smtp_to": "YOUR_ADDRESS@gmail.com"
}

これは既存設定へ追加・変更する項目の抜粋です。このJSONだけで設定全体を置き換えないでください。

このSMTP実装はOAuth機能を持たないため、送信元の2段階認証を有効にしたうえでアプリパスワードを利用します。Google公式説明を確認し、アプリパスワード画面で「Piログ監視」などの名前で作成します。アカウント条件によって利用できない場合があります。

作成した16文字はチャットや記事に貼らず、Piのターミナルで直接入力します。次のコマンドは通常のGoogleパスワードではなくアプリパスワードを受け取ります。入力文字は表示されません。

実施場所:Raspberry Pi 4/SSH

sudo python3 -c 'import getpass,os,pathlib; u=input("Gmail address: ").strip(); assert u.endswith("@gmail.com") and not any(x.isspace() for x in u); p=getpass.getpass("Gmail app password: ").replace(" ",""); assert len(p)==16 and p.isascii() and p.isalpha(),"Invalid password format"; os.umask(0o077); f=pathlib.Path("/etc/router-monitor/smtp.env"); f.write_text("SMTP_USERNAME="+u+"\nSMTP_PASSWORD="+p+"\n"); f.chmod(0o600); print("Saved")'

/etc/router-monitor は事前に作成したroot管理のディレクトリです。保存ファイルはroot所有・0600とし、サービスのEnvironmentFileから読み込みます。内容を画面表示したりGitへ登録したりしません。

次項の手順で、ファイル権限→STARTTLS→認証→テスト1通送信→受信箱の到着を確認します。その後にnotification_modeをsmtpへ変更してサービスを再起動します。

テストの件名は「[Piログ監視] メール通知テスト」、通常の監視通知は種類別の件名を使います(末尾の「現行の通知を読む」を参照)。通知本文には集計と理由を含め、生ログは載せません。Gmail APIは実装していません。

テスト送信と有効化を分ける 【Raspberry Pi 4/SSH・スマートフォン・PC】

Piで /etc/router-monitor/config.json の送信先・送信元・SMTP項目を設定します。notification_mode は到着確認まで dry-run を維持します。

実施場所:Raspberry Pi 4/SSH

sudo vi /etc/router-monitor/config.json

次のコードは実際にメールを1通送る手順です。認証ファイルを読みますが、秘密の値は表示しません。宛先を確認してから実行してください。

sudo python3 - <<'PY'
import json
import smtplib
import ssl
import stat
from pathlib import Path
from email.message import EmailMessage
p = Path('/etc/router-monitor/smtp.env')
assert p.stat().st_uid == 0
assert stat.S_IMODE(p.stat().st_mode) == 0o600
credentials = dict(line.split('=', 1) for line in p.read_text().splitlines() if line)
c = json.loads(Path('/etc/router-monitor/config.json').read_text())
assert c['smtp_host'] == 'smtp.gmail.com'
assert c['smtp_port'] == 587 and c['smtp_security'] == 'starttls'
m = EmailMessage()
m['Subject'] = '[Piログ監視] メール通知テスト'
m['From'] = c['smtp_from']
m['To'] = c['smtp_to']
m.set_content('Raspberry Pi 4からの通知テストです。攻撃検知通知ではありません。')
with smtplib.SMTP(c['smtp_host'], c['smtp_port'], timeout=30) as server:
    server.starttls(context=ssl.create_default_context())
    server.login(credentials['SMTP_USERNAME'], credentials['SMTP_PASSWORD'])
    rejected = server.send_message(m)
print('SMTP accepted; rejected recipients:', len(rejected))
PY

rejected recipients: 0 はサーバー受付の確認で、受信箱に届いたことの証明ではありません。受信者が件名を確認してから次へ進みます。

sudo python3 - <<'PY'
import json
from pathlib import Path
p = Path('/etc/router-monitor/config.json')
c = json.loads(p.read_text())
c['notification_mode'] = 'smtp'
p.write_text(json.dumps(c, indent=2) + '\n')
PY
sudo systemctl restart router-monitor
sudo systemctl is-active router-monitor

この単独メール試験は監視DBのキューを経由しません。単独試験の成功だけで、実イベントの検知から送信までを検証したとは扱わないでください。

11. PCのDockerに学習・評価・自動配布を配置する 【Windows PC/Docker】

PiへSSH接続中の端末を使う場合は exit でWindows PowerShellへ戻ってから実行します。

PC側のPython処理は raspberrypi4-secure コンテナで実行します。Pi側のログ監視をPCから起動する構成ではありません。ここではコンテナを準備し、第12章でPi側の受け口を配置してから実接続を確認します。

11-1. Python 3.13から実行イメージを作る

docker/ml-runtime.Dockerfile は次の内容で用意します。Pythonの公式イメージをベースに、学習用ライブラリ、SSH用ライブラリ、対応するソースを配置します。

FROM python:3.13-slim

WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt \
    paramiko==4.0.0 bcrypt==5.0.0 cryptography==50.0.2 \
    invoke==3.0.3 pynacl==1.6.2 cffi==2.1.1 pycparser==3.11

COPY router_monitor ./router_monitor
COPY scripts ./scripts
COPY tests ./tests
COPY security_monitor.py pi_behavior.py antivirus_status.py pi_antivirus.py windows_collector.py docker_collector.py ./

ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
CMD ["python", "-m", "unittest", "discover", "-s", "tests", "-v"]

requirements.txt は学習に使用するライブラリを固定します。

numpy==2.2.6
pandas==2.2.3
scikit-learn==1.6.1
scipy==1.15.3
joblib==1.4.2
threadpoolctl==3.6.0

ビルドコンテキストへ実ログ・状態・認証設定を渡さないよう、プロジェクト直下の .dockerignore も用意します。資格情報用のDocker volumeは、このフォルダーとは別に保存されます。Docker公式のビルドコンテキストと除外設定

.git/
.venv/
**/__pycache__/
data/
models/
state/
.env
*.env
config.local.json
config.pipeline.json
*.db
*.db-*
*.joblib

実施場所:Windows PC/PowerShell

Set-Location C:\work\RaspberryPi4-Secure
docker build -t raspberrypi4-secure-trainer:ml-20261021 -f docker/ml-runtime.Dockerfile .

ビルドにはインターネット接続が必要です。イメージの標準コマンドはテストですが、次項のComposeでは週次学習・配布用の router_monitor.pipeline をメインプロセスとして起動します。

PiのJSONモデルによる判定は標準ライブラリで動作するため、学習済みモデルによる判定のためだけにPiへscikit-learn等を追加する必要はありません。数値データの項目・計算方法とJSON形式、数値一致の検査は引き続き必要です。

11-2. Composeの内容を確認する

対応版の compose.dev.yaml は次の構成です。

name: raspberrypi4-secure-development
services:
  development:
    image: raspberrypi4-secure-trainer:ml-20261021
    container_name: raspberrypi4-secure
    working_dir: /workspace
    command: [python, -m, router_monitor.pipeline, --config, /workspace/config.pipeline.json]
    restart: unless-stopped
    init: true
    read_only: true
    user: "10001:10001"
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    mem_limit: 1g
    cpus: 2
    pids_limit: 128
    environment:
      PYTHONUNBUFFERED: "1"
      PYTHONDONTWRITEBYTECODE: "1"
    tmpfs:
      - /tmp:size=256m,mode=1777
    volumes:
      - .:/workspace
      - ml-secret:/run/router-ml-secret
    logging:
      driver: json-file
      options:
        max-size: 1m
        max-file: "2"

volumes:
  ml-secret:
    name: raspberrypi4-secure-ml-secret

非root、読み取り専用のルートファイルシステム、CPU・メモリ・PID・ログ量の制限を設けます。ソースと状態は /workspace、SSHパスワードは別volumeです。ポートは公開せず、PiへのSSH通信にDockerのbridgeネットワークを使います。

11-3. 資格情報用volumeとコンテナを準備する

まず専用volumeを作り、実行ユーザーが利用できる所有者にします。この一時コンテナはvolumeの所有者変更だけを行い、Pythonの学習や監視は起動しません。

実施場所:Windows PC/PowerShell

docker volume create raspberrypi4-secure-ml-secret

docker run --rm --network none --cap-drop ALL --cap-add CHOWN `
  -v raspberrypi4-secure-ml-secret:/secret `
  --entrypoint chown raspberrypi4-secure-trainer:ml-20261021 `
  10001:10001 /secret

docker compose -f compose.dev.yaml config --quiet
docker compose -f compose.dev.yaml up -d development

初回は資格情報やPi側の補助プログラムが未設定なので、学習処理の失敗が記録される場合があります。Piの既存監視は止まりません。以下の設定と第12章を終えてから、手動の1回確認で正常な接続・条件判定を確認します。

実施場所:Windows PC/PowerShell(入力はコンテナ内で受け付ける)

docker exec -it raspberrypi4-secure python -c 'import getpass,os,pathlib; os.umask(0o077); d=pathlib.Path("/run/router-ml-secret"); d.chmod(0o700); p=d/"pi-password"; p.write_text(getpass.getpass("Pi SSH password: ")); p.chmod(0o600); print("Saved")'

入力するのはPiのSSHログインパスワードです。Gmailのアプリパスワードではありません。パスワードは画面やコマンド引数へ表示せず、ソース・イメージ・Gitへ含めません。volumeを削除・移設した場合は再設定が必要です。SSH認証鍵は作成しません。

11-4. Piの接続先とホスト指紋を設定する

Pi本体で確認した ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub の SHA256:... を使用します。接続先から無検証で取得した値を、そのまま正しい指紋として登録しないでください。

実施場所:Windows PC/PowerShell

docker exec -it raspberrypi4-secure python -c 'import json,pathlib; p=pathlib.Path("config.pipeline.json"); c=json.loads(p.read_text()); c["host"]=input("Pi IP: ").strip(); c["user"]=input("SSH user: ").strip(); c["host_fingerprint"]=input("Verified SHA256 fingerprint: ").strip(); assert c["host_fingerprint"].startswith("SHA256:"); p.write_text(json.dumps(c,indent=2)+"\n"); print("Saved")'

設定例の構造です。指紋の例示値を実行用設定へそのまま入れないでください。

{
  "host": "192.168.11.11",
  "user": "pi",
  "host_fingerprint": "SHA256:Pi本体で確認した指紋",
  "state_directory": "/workspace/state/ml-pipeline",
  "password_file": "/run/router-ml-secret/pi-password"
}

本プロジェクトのPi側の補助プログラムと配備スクリプトはユーザー pi 専用の受信フォルダーを前提にしています。別ユーザーを使う場合は、JSONだけでなく配備スクリプトの所有者・パスも対応させます。

接続設定を保存したら、常駐プロセスへ反映します。pipeline は起動時に設定ファイルを読み込むため、手動の --once だけでは常駐側の接続設定は更新されません。

実施場所:Windows PC/PowerShell

docker compose -f compose.dev.yaml restart development

11-5. コンテナ内でテストする

実施場所:Windows PC/PowerShell

docker exec -w /workspace raspberrypi4-secure python -m unittest discover -s tests -v

模擬データを使うテストでは、Loglizer件数とPCとPiが使う数値データの一致、学習とJSONモデルによる判定の一致、品質不合格時の拒否、更新前モデル保持、ルール監視の継続、週次予定時刻を確認します。生成データ上のテスト成功は、実ログでの検知精度を示しません。

模擬ログで作ったモデルを、本番の current.json へ置かないでください。 テストは一時領域で実施し、実運用のパイプラインはPiから取得した集計を対象にします。

12. Piの状態確認・週報とモデルの受け口を配置する 【Raspberry Pi 4/SSH】

実施場所:Windows PC/PowerShell(Piへ接続)

ssh pi@192.168.11.11

以降、Pi側と記載したブロックをこのSSH接続先で実行します。Windows側の操作は別のPowerShellを開くか、exit で戻ってから行ってください。

12-1. 状態確認サービスを配置する

第4章で転送した対応版ソースを使います。第10章のSMTP単独試験と受信確認を先に済ませます。

実施場所:Raspberry Pi 4/SSH

cd ~/router-monitor-bootstrap
sudo install -d -m 0755 /opt/security-status
sudo install -d -m 0700 /etc/security-status /var/lib/security-status
sudo install -m 0644 security_monitor.py pi_behavior.py antivirus_status.py pi_antivirus.py /opt/security-status/
sudo install -m 0644 pi/security-status.service /etc/systemd/system/security-status.service
sudo install -d -m 0755 /etc/systemd/system/security-status.service.d
sudo install -m 0644 pi/security-status-behavior.conf /etc/systemd/system/security-status.service.d/behavior.conf

pi/security-status-behavior.conf は次の内容です。systemdの追加設定として読み込みます。

[Service]
CapabilityBoundingSet=CAP_NET_RAW CAP_SYS_PTRACE

CAP_NET_RAW は既存の疎通確認、CAP_SYS_PTRACE は別ユーザーの /proc/PID/exe を読み取るために使用します。実装ではプロセスの追跡・メモリ読取り・操作は行いません。ただし、この権限自体はそれらの操作にも使えるため、管理者が用途を理解して許可した環境で設定します。サービスの NoNewPrivileges・ファイルシステム保護など、他の制限は維持します。権限やOSの制限で読めない場合は、確認不能として扱います。

新規環境用の設定を作ります。既存ファイルがある場合は上書きせず停止する例です。2台目のルーターを使わない環境では gateway_ip を実際の確認対象に合わせてください。

sudo python3 - <<'PY'
import json
from pathlib import Path
p=Path('/etc/security-status/config.json')
if p.exists():
    raise SystemExit('既存設定があります。上書きせず内容を確認してください。')
old=json.loads(Path('/etc/router-monitor/config.json').read_text())
assert old['notification_mode']=='smtp', '先にGmail設定と受信確認を完了してください'
c={
    'database':'/var/lib/security-status/status.db',
    'router_ip':old['router_ip'],
    'gateway_ip':'192.168.0.1',
    'log_path':old['log_path'],
    'windows_monitoring_enabled':False,
    'mrt_weekly_enabled':False,
    'behavior_monitoring_enabled':True,
    'notification_mode':'dry-run',
    'analytics_database':old['state_path'],
    'analytics_pipeline_status':'/var/lib/router-ml/pipeline-status.json',
}
for k in ('smtp_host','smtp_port','smtp_from','smtp_to'):
    c[k]=old[k]
p.write_text(json.dumps(c,ensure_ascii=False,indent=2)+'\n')
p.chmod(0o600)
PY
sudo systemd-analyze verify /etc/systemd/system/security-status.service
sudo systemctl daemon-reload
sudo systemctl enable --now security-status
sudo systemctl is-active security-status
sudo journalctl -u security-status -n 20 --no-pager

Windowsからの情報受信は無効なので、Windows用TLS受信ポートは起動しません。K7状態取得、Windowsの検査タスク、PowerShell補助処理、DDoS監視は設定しません。

12-2. 追加チェックの内容と通知条件

Piは約5分ごとにBUFFALO・外側ルーターへの疎通、rsyslog・router-monitor、UDP 514、ログ読取りを確認します。これらの基本状態の異常は2回連続で要確認通知にし、同じ状態を繰り返し送らないよう保存します。認証ログ・実行ファイルは、以下の別条件で約1分ごとに確認します。

主要パッケージの整合性確認は約1日ごとです。設定以外の変更が見つかった場合は優先度の高い確認対象としますが、正規の更新や操作との照合が必要です。整合性一致も感染なしの証明ではありません。

認証ログと実行ファイルで確認すること

pi_behavior.py は security-status の処理内で動きます。Pi自身のsystemd journalから sshd・sshd-session・sudo の認証記録を読みます。ルーターから転送されるSyslogの分類・集計とは別の確認です。

対象 通知する条件 通知だけで断定しないこと
SSH認証失敗関連の記録 直近5分に10件以上 入力間違いや設定不一致でも増える。1回の認証試行で複数行出ることがあり、10件=10回の試行とは限らない
sudo認証失敗 直近5分に5件以上 管理者本人のパスワード誤入力でも発生する
rootへのSSHログイン成功 成功記録を取得したとき 管理者の正規操作の可能性もある。身に覚えがあるか確認する
一時フォルダー内の実行ファイル /tmp/・/var/tmp/・/dev/shm/ 配下のファイルから実行中 インストーラーなど正常な処理の可能性もある
削除済みの実行ファイル /proc/PID/exe の参照先が (deleted) になっている パッケージ更新後、更新前のプログラムが動き続けている場合もある

SSH・sudoの件数が基準を超えた状態の間は、同じ条件で繰り返し通知しません。基準未満へ戻ってから再び超えた場合は、別の発生として通知します。rootログインは同じログ、実行ファイルは同じPID・プロセスの起動時刻・参照先の組合せについて、送信記録が残っている間は重複を抑えます。読取り失敗は「検出なし」にせず、状態が確認不能へ変わったときに通知します。

通常ユーザーのSSHログイン成功も件数として記録しますが、成功しただけでは不審と判定しません。認証ログ本文・コマンド引数・環境変数・実行ファイルのパスは通知用DBへ保存しません。実行ファイルの要確認メールにはPIDと理由を記載します。ClamAVの検出メールでは、対応に必要な対象ファイル名と検出名を記載します。

これはプログラムの安全性を網羅的に判定する検査ではありません。 通常の場所に置かれたマルウェア、Python等で実行されるスクリプトの内容、通信先の安全性、確認間隔の間に終了したプログラムは判定対象外です。初回の認証ログ確認は監視開始時点からで、停止・遅延後の追跡は最大で直近5分です。大量のログで上限に達した場合も確認不能とします。

dry-runで結果と権限を確認する

実施場所:Raspberry Pi 4/SSH

cd ~/router-monitor-bootstrap
python3 -m unittest discover -s tests -p test_pi_behavior.py -v
systemctl show security-status -p CapabilityBoundingSet
sudo python3 - <<'PY'
import json,sqlite3,time
from pathlib import Path
config=json.loads(Path('/etc/security-status/config.json').read_text())
db=sqlite3.connect('file:'+config['database']+'?mode=ro',uri=True)
row=db.execute("SELECT value FROM state WHERE key='behavior_status'").fetchone()
if row:
    result=json.loads(row[0])
    print('確認からの経過秒:',round(time.time()-result['checked_at']))
    print(json.dumps(result,ensure_ascii=False,indent=2))
else:
    print('まだ確認結果がありません。サービスとjournalを確認してください。')
db.close()
PY

checks の「認証ログ」「実行ファイル確認」が ok か、unreadable が0かを確認します。process_signals が0でも、今回の条件に該当する兆候がなかったという意味です。感染なしとは判定しません。約1分後にもう一度確認し、checked_at が更新されることも確認します。unknown や更新停止があれば、権限・ログ量・サービスの実行エラーを確認します。

dry-runで動作を確認したら、本番送信へ切り替えます。通知先は第10章で確認したアドレスです。

sudo python3 - <<'PY'
import json
from pathlib import Path
p=Path('/etc/security-status/config.json')
c=json.loads(p.read_text())
c['notification_mode']='smtp'
p.write_text(json.dumps(c,ensure_ascii=False,indent=2)+'\n')
p.chmod(0o600)
PY
sudo systemctl restart security-status

設定確認レポートを1回キューへ入れる例です。既存サービスがGmailへ送信します。

sudo python3 - <<'PY'
import json,sys,time
from pathlib import Path
sys.path.insert(0,'/opt/security-status')
from security_monitor import Agent
c=json.loads(Path('/etc/security-status/config.json').read_text())
a=Agent(c)
subject,body=a.report(time.time())
with a.db:
    a.queue('setup:'+str(time.time()),'【設定確認】'+subject,body)
a.db.close()
print('送信キューへ登録しました。受信箱で到着を確認してください。')
PY

2種類の通知をテストする

通常の要確認メールと混同しないよう、件名・本文に「通知テスト」「実検出ではありません」と明記します。送信先を確認し、SMTPへ切り替えた後に実行します。以下はウイルスや認証攻撃を発生させず、メールの表示と送信経路だけを確認する手順です。

実施場所:Raspberry Pi 4/SSH → スマートフォン・PCで受信確認

sudo python3 - <<'PY'
import json,sys,time
from pathlib import Path
sys.path.insert(0,'/opt/security-status')
from security_monitor import Agent
config=json.loads(Path('/etc/security-status/config.json').read_text())
assert config['notification_mode']=='smtp', '送信先を確認してからSMTPを有効にしてください'
print('通知先:',config['smtp_to'])
a=Agent(config)
key='behavior-test:'+str(time.time())
with a.db:
    a.queue(key+':behavior',
        '【通知テスト】不審な動作の兆候(実検出ではありません)',
        'これは通知テストです。実際の検出ではありません。不審な動作の兆候と、ClamAVの検出を区別して通知します。')
    a.queue(key+':antivirus',
        '【通知テスト】ウイルス検査での検出(実検出ではありません)',
        'これは通知テストです。実際の検出ではありません。ClamAVが対象ファイルで検出した場合は、別の件名で要確認通知を送ります。')
a.db.close()
print('テスト2通を登録しました。既存サービスが送信します。受信箱で確認してください。')
PY

認証やプログラムの兆候は「普段の操作で説明できるか」を確認する通知、ClamAVは「対象ファイルを検査した結果の検出」を知らせる通知です。どちらも自動で感染を確定したり、プロセス停止・ファイル削除・通信遮断を行ったりはしません。

12-3. モデルの受け口と配布用の補助プログラムを配置する

監視コードは第7章、状態確認サービスは第12章の前半で配置済みです。ここでは、モデル受信用ディレクトリと用途を限定した補助プログラムを配置します。

実施場所:Raspberry Pi 4/SSH

cd ~/router-monitor-bootstrap
sudo install -d -o root -g root -m 0755 /opt/router-monitor/models
sudo install -d -o pi -g pi -m 0700 /var/lib/router-ml /var/lib/router-ml/inbox
sudo install -o root -g root -m 0755 scripts/router-ml-admin /usr/local/sbin/router-ml-admin
sudo /usr/local/sbin/router-ml-admin summary
systemctl is-active router-monitor security-status rsyslog

この補助プログラムは export(確定集計の取得)、activate(候補検査・切替)、status(学習状態の保存)、rollback(更新前モデルへの復元)などに用途を限定しています。コンテナ側はユーザー pi のSSHパスワードとsudoを使い、パスワードなしsudoや認証鍵を追加しません。

ルール用集計は windows、補助解析用の数値データは analytics_windows、統計・ML結果は analytics_predictions へ保存します。各テーブルは集計する項目が異なります。機械学習の準備状況は、学習に使える集計区間の数で確認します。

12-4. コンテナから実接続を1回確認する

実施場所:Windows PC/PowerShell

docker exec raspberrypi4-secure python -m router_monitor.pipeline --config /workspace/config.pipeline.json --once
docker logs --tail 10 raspberrypi4-secure

データが少なければ status: insufficient_data と学習に使える集計区間の数が表示されます。これはモデルを作らず安全に見送った結果であり、ルーター受信の故障を意味しません。failed が表示される場合はホスト指紋、IP、認証、sudo、補助プログラムの配置を確認します。

実施場所:Raspberry Pi 4/SSH

sudo /usr/local/sbin/router-ml-admin summary
sudo cat /var/lib/router-ml/pipeline-status.json

学習側の結果がPiへ届いていることまで確認します。SSH接続成功だけで、自動配布の全経路を確認したとはしません。

12-5. 自動更新の実行順序

  1. コンテナが毎週月曜02:00 JSTの枠を、正常稼働時は約5分間隔で確認する。
  2. Piのホスト指紋を照合し、SSHで確定済み集計と現行モデル情報を取得する。
  3. データ量・期間・新しく増えた集計区間の数の条件を確認する。
  4. 条件を満たせばLoglizerで解析用の数値データを作り、Isolation Forestを学習する。
  5. 判定基準を調整する期間で境界を決め、未使用の後続期間で候補を評価する。
  6. 合格した場合だけJSONモデルをSFTPでPiの受信フォルダーへ保存する。
  7. Piの補助プログラムが形式・記録された評価合格条件・最小データ数とモデルの判定動作テストを確認する。実ログによる学習・評価の再実行はコンテナ側で行い、Piでは行わない。
  8. 更新前モデルを previous.json へ残し、current.json を一括切替する。
  9. 学習の見送り・不合格・配布結果をPiへ返し、週報へ反映する。

データ不足・評価不合格は、完了した確認として次週まで待ちます。例外時は1時間以上空けて再試行し、現行モデルを維持します。初回にモデルがなければ、ルール・統計処理だけで継続します。補助解析の記録・評価で捕捉した例外は分離して保存し、既存ルールの判定を継続します。監視プロセス自体の停止や設定・コード不整合まで継続を保証するものではありません。

コンテナの実行状態と予定状態はPCで確認できます。

docker ps --filter name=raspberrypi4-secure
docker exec raspberrypi4-secure python -c 'import json,pathlib; print(json.loads(pathlib.Path("state/ml-pipeline/schedule.json").read_text()))'

PC停止中は学習・配布できません。次回起動時に最新枠を確認しますが、Docker Desktop自体の起動も必要です。restart: unless-stopped は、利用者が手動停止したコンテナを無条件に起動する設定ではありません。

12-6. Pi自身のClamAV定期検査を配置する

この検査は、Piの対象フォルダーにあるファイルを調べるものです。Windows PCや家庭内の全端末を遠隔検査する機能、常時アクセス監視、DDoS検査ではありません。学習モデルの有無にも依存しません。

約2GBメモリのPiで、定義を読み込む処理の負荷を確認してから使います。本実装は常駐のclamdを使わず、定期的にclamscanを起動します。ClamAV公式のスキャン解説

① 修正版のエンジンとパッケージを確認する(Pi)

sudo apt update
apt-cache policy clamav clamav-freshclam

導入する版が使用中OS向けに提供された修正版か、DebianのClamAVセキュリティ情報で確認します。実機で確認した版はClamAV 1.4.6です。実機ではDebian署名付きの trixie-proposed-updates を利用し、APTの優先設定で clamav・clamav-base・clamav-freshclam・libclamav12 の4パッケージだけを対象にしました。OS全体を同リポジトリへ切り替えた構成ではありません。この版番号は将来の最新番号を示すものではありません。候補版に既知の未修正問題がある場合は、このClamAV導入工程を止め、OSの修正版提供状況を確認してください。検証環境の追加リポジトリ設定を無条件にコピーしません。以下のインストールは、利用する候補版を確認できた場合に実施します。ログ監視・学習はClamAVの導入とは独立して進められます。

sudo apt install --no-install-recommends clamav clamav-freshclam
clamscan --version

② メモリ制限を実際に適用できることを確認する(Pi)

cat /sys/fs/cgroup/cgroup.controllers
free -h

一覧に memory がなければ、MemoryMaxの指定だけでは制限を適用できません。検査はその状態では開始しない実装です。今回のPiでは、次の設定を追加して再起動しました。自環境で memory が既に表示されていれば、この変更は不要です。

sudo python3 - <<'PY'
from pathlib import Path
p=Path('/boot/firmware/cmdline.txt')
b=p.with_name('cmdline.before-pi-antivirus.txt')
text=p.read_text()
if not b.exists():
    b.write_text(text)
tokens=text.strip().split()
for value in ('cgroup_enable=memory','cgroup_memory=1','swapaccount=1'):
    if value not in tokens:
        tokens.append(value)
p.write_text(' '.join(tokens)+'\n')
PY
sudo reboot

再起動中はPiの監視が止まります。SSHで再接続したら memory の表示、rsyslog・router-monitor・security-statusの稼働を確認します。ブート引数の他の値を記事の例で置き換えないでください。

③ 実行ディレクトリ・定義更新設定を用意する(Pi)

sudo install -d -o root -g root -m 0700 /var/lib/pi-antivirus /var/log/pi-antivirus
sudo install -d -o root -g root -m 0755 /etc/pi-antivirus
sudo tee /etc/pi-antivirus/freshclam.conf >/dev/null <<'CONF'
DatabaseDirectory /var/lib/clamav
DatabaseOwner clamav
DatabaseMirror database.clamav.net
ScriptedUpdates yes
TestDatabases no
MaxAttempts 2
ConnectTimeout 15
ReceiveTimeout 60
CONF
sudo chmod 0644 /etc/pi-antivirus/freshclam.conf

TestDatabases no は更新時に追加のエンジン読込みを行わないための設定です。実際の検査時に定義を読み込み、ファイル検査を行います。

④ 更新・検査のsystemdユニットを配置する(Pi)

cd ~/router-monitor-bootstrap
sudo install -m 0644 pi/pi-antivirus-*.service pi/pi-antivirus-*.timer /etc/systemd/system/
sudo systemd-analyze verify /etc/systemd/system/pi-antivirus-*.service /etc/systemd/system/pi-antivirus-*.timer
sudo systemctl daemon-reload
sudo systemctl enable --now pi-antivirus-update.timer
sudo systemctl start pi-antivirus-update.service
sudo cat /var/lib/pi-antivirus/update.json

status: updated を確認します。パッケージ付属のfreshclamが更新中なら同時に起動せず、その処理の終了を確認してから試験します。独自の更新処理の成功を確認した後にだけ、二重起動を避けるため既存の自動更新を無効化します。サービス名はOSによって異なるため、存在するものを確認してください。

systemctl list-unit-files 'clamav-freshclam*'
systemctl list-timers --all '*clam*'

検証環境で存在したパッケージ付属のユニットは次のとおりでした。独自更新が失敗している状態では無効化しません。

sudo systemctl disable --now clamav-freshclam-once.timer clamav-freshclam-once.service clamav-freshclam.service

⑤ 検査結果の通知・週報への連携を有効にする(Pi)

sudo python3 - <<'PY'
import json
from pathlib import Path
p=Path('/etc/security-status/config.json')
c=json.loads(p.read_text())
c['antivirus_directory']='/var/lib/pi-antivirus'
p.write_text(json.dumps(c,ensure_ascii=False,indent=2)+'\n')
p.chmod(0o600)
PY
sudo systemctl restart security-status

本番の初回検査より前に連携を有効にします。初回の結果がまだない間は未完了と表示し、自己テストの結果は本番検出として扱いません。

⑥ 自己テストを確認し、定期検査を有効にする(Pi)

次に自己テストを実行します。独自スクリプトはPiの一時的なテストファイルを使い、終了時に削除します。実際のウイルスを持ち込む試験ではありません。自己テストの検出を本番のウイルス検出メールへ流さない構成です。

sudo systemctl start pi-antivirus-selftest.service
sudo cat /var/lib/pi-antivirus/selftest.json

status: passed と memory_limit_enforced: true、メモリ・swap・監視サービスの状態を確認した後、週次検査を有効化します。

sudo systemctl enable --now pi-antivirus-scan.timer
sudo systemctl start --no-block pi-antivirus-scan.service
systemctl list-timers --all 'pi-antivirus-*'

検査は時間がかかる場合があります。起動コマンドが戻ったことだけで完了とはしません。

項目 内容
定義更新 毎日01:30 JST
対象検査 毎週日曜03:00 JST
対象 /home /opt /usr/local /etc /usr/bin /usr/sbin /tmp /var/tmp
workerのCPU上限 1コア基準で35%
workerのメモリ上限 MemoryMax 1280M、MemoryHigh 1100M、swap上限512M
異常時 空きメモリ低下・監視停止等で中断し、未完了を記録
自動削除・隔離 行わない

別ファイルシステムやシンボリックリンクは辿らず、ファイルサイズ・アーカイブ・時間等にも制限を設けています。暗号化や上限到達を、検査済み・検出なしとして扱いません。対象外ファイルや感染全般の有無を保証する検査ではありません。

検査中は running.json、完了後は result.json、定義更新は update.json へ保存します。結果ファイルがまだない場合は初回検査未完了です。ルール・統計・学習の結果と混同しないでください。

13. 日常運用・異常時の確認・モデルの戻し方

13-1. 普段の運用

ルーターとPiを常時稼働させ、週次報告を確認します。PCは学習枠に動いていればよく、停止中もPiの収集・ルール監視・認証ログと実行ファイルの確認・統計判定・既に採用したモデルによる判定・定期ファイル検査は続きます。

タイミング 人が確認すること
要確認メールが届いたとき 件名・対象項目・時刻を読み、心当たりの操作と元の記録を照合
月曜の週次報告 基本チェック、認証ログ・実行ファイルの確認結果、学習に使える集計区間の数、学習の見送り・合否、検査未完了の有無
学習処理の結果が9日以上更新されない PC、Docker、コンテナ、SSH接続、予定状態を確認
機器の追加・設定変更後 その変化がログや統計に出たか確認し、異常と即断しない
OSやコードの更新後 サービス・数値データの項目・計算方法・モデルの判定動作テストとログ受信を再確認

実施場所:Raspberry Pi 4/SSH

systemctl is-active router-monitor security-status rsyslog
sudo journalctl -u router-monitor -u security-status -n 40 --no-pager
sudo /usr/local/sbin/router-ml-admin summary
sudo cat /var/lib/router-ml/pipeline-status.json
sudo ls -lh /var/log/buffalo.log*
df -h

補助解析のエラーは、集計結果と処理状態を読取り専用で確認できます。週報のモデルIDは直近にモデルで判定した実績を示し、配布直後のファイル切替確認やすべてのモデル読取りエラーを代替するものではありません。

実施場所:Raspberry Pi 4/SSH

sudo python3 - <<'PY'
import sqlite3
c=sqlite3.connect('file:/var/lib/router-monitor/monitor.db?mode=ro',uri=True)
print('処理エラー:',c.execute("SELECT key,value FROM kv WHERE key IN ('analytics_error','analytics_ingest_error')").fetchall())
print('直近の推論:',c.execute("SELECT start,model_id,error FROM analytics_predictions ORDER BY start DESC LIMIT 5").fetchall())
c.close()
PY

記事や問い合わせへ共有する際は、生ログ、資格情報、端末名、MAC、個人のアドレスを除きます。単に active であることと、実ログが届き正常に判定できていることは別です。

13-2. モデルがない・更新されない場合

状態 動作・対応
データ不足 ルールと解析用データの蓄積を継続。条件を満たした集計区間の数・期間を待つ
統計の履歴不足 学習に使える集計区間が30区間に達するまで、過去との比較判定を保留
評価不合格 現行モデルを保持。合格させる目的で基準を無条件に緩めない
初回でモデルなし 学習済みモデルによる判定だけを行わず、ルール監視を継続
PC停止 Pi監視は継続。次回コンテナ起動後に学習枠を確認
SSH・転送失敗 IP・指紋・認証・sudo・補助プログラムを確認。更新前モデルを維持
JSONモデル読取り不良 補助解析の記録を確認し、必要なら更新前モデルへ戻す。ルールは継続

本構成は、ログの量だけでなく観測期間・変動・評価を条件にしています。同じ種類のログが多数あるだけでは、役に立つモデルができたとは判断しません。

13-3. 更新前モデルへ戻す

実施場所:Raspberry Pi 4/SSH

sudo /usr/local/sbin/router-ml-admin rollback
sudo /usr/local/sbin/router-ml-admin summary

previous.json が存在する場合に使います。一度もモデルを採用していなければ復元対象はありません。ファイル切替後、次の集計区間の評価で読み取ります。モデルを戻しても、既に送信されたメールは取り消されません。

13-4. メール設定を変更する

送信先を変えるときは /etc/router-monitor/config.json と /etc/security-status/config.json の smtp_to を合わせます。送信元変更時は両方の smtp_from と、送信元アカウントのSMTP認証情報も更新します。通知先変更だけならアプリパスワードの作り直しは不要です。

sudo systemctl restart router-monitor security-status

一時的に送信を止める場合は、両サービスの notification_mode を dry-run へ変更して再起動します。router-monitor の送信キューは simulated と sent を区別しますが、security-status はdry-runで処理した場合も送信処理済みの時刻を記録します。その時刻だけでSMTP受付成功と判断しないでください。dry-runで処理済みの通知は、smtpへ戻しても自動で一括実送信されません。SMTP送信待ちの通知は再開後に送られる場合があるため、時刻・件数を確認します。

14. 届くメールの意味と最初の対応 【スマートフォン・PC】

件名・種類 何が起きたか 最初に確認すること
【要確認・優先度高】認証失敗相当… ログの文字列分類と件数がルールに該当 誤入力・設定不一致・見覚えのない試行がないか
【注意・要確認】通信拒否相当… 拒否・遮断に相当する記録が増加 通信の集中やアプリの再試行。遮断記録を侵入成功と混同しない
【要確認・監視異常】Pi: …のチェック異常 状態チェックで継続した異常・確認不能 本文のサービス・疎通・受信口・読取りを確認
【緊急・要確認】Piの主要パッケージ… 設定以外の変更等を整合性確認で報告 正規の更新・管理操作との照合
【不審な動作の兆候・要確認】Piで… SSH・sudoの失敗集中、rootログイン、一時フォルダー・削除済みファイルからの実行 自分の操作・更新と照合。感染と断定しない
【監視確認不能】Piの…を確認できません 認証ログや実行ファイルを読み取れない 権限・ログ量・サービスを確認。検出なしと扱わない
【ウイルス検査での検出・優先度高】Piのファイルを要確認 ClamAVが対象ファイルで検出 ファイル名・検出名・利用状況を確認。実行・感染の確定ではない
【要確認・検査未完了】… 検査の中断・エラー・制限等で完了しなかった 実行結果、空きメモリ、制限、検査ログ
【要確認・定義更新失敗】… 定義更新が完了しなかった ネットワーク、更新サービス、定義の状態
【週次報告・定期チェック結果】… 定期状態と解析・検査結果をまとめた報告 集計区間の数、データ不足、モデル適用状況、未確認項目
【週次報告・要確認・未確認項目あり】… 週報に異常や未確認の項目がある どの項目が未確認なのか本文を確認

ルーターの認証失敗・拒否のルール通知は集計確定後です。Pi自身の認証ログ・実行ファイルの兆候は約1分ごとの確認後、基本状態の異常は追加チェックの連続条件を満たした後、ClamAVの検出は検査中の結果確認後に通知します。通常の状態と認証記録の件数・実行ファイルの確認結果は週報へまとめ、正常な確認のたびにはメールを送りません。SMTP失敗時は約5分後に再試行します。件名の優先度は確認順序の目安であり、攻撃成功率ではありません。

統計・機械学習の結果だけで「通常と異なるログ傾向」という即時メールは送りません。 当面は記録と週報で確認します。平常時の「ログ未受信・基本チェック正常」メールも抑制しています。それらが繰り返し届く場合は、監視サービスの二重起動や health_notifications_enabled の設定を確認してください。

週報の「データ不足」「モデル未適用」は、学習開始条件や採用条件を満たしていない説明です。「危険を検出した」「すべて安全」のどちらも意味しません。SMTP送信成功と受信箱到着も別なので、初回設定では受信者が実際のメールを確認します。

15. 確認できたこと・まだ確認していないこと

15-1. 実装時の確認結果

項目 確認した内容
コンテナのテスト 対応ソースで76件成功。認証・実行ファイル監視の6件を含む
掲載Dockerfile Python公式イメージからの独立ビルドに成功。稼働環境への切替試験とは別
掲載Compose 構成検証に成功
Piのテスト 既存64件と、認証・実行ファイル監視の追加6件をそれぞれ成功確認
PCとPiで共通の数値データ Loglizerのイベント件数と共通処理の一致
学習済みモデルによる判定 生成データ上でscikit-learnとJSONモデルによる判定の数値一致
更新制御 データ不足・評価不合格の拒否、更新前モデル保持、二重実行抑止
実機連携 コンテナからの指紋照合・集計取得・データ不足判定・状態返送
既存監視 router-monitor・security-status・rsyslogの稼働を確認
Piの認証・実行ファイル監視 実サービスの権限制限下で動作。確認時は64〜68プロセスの実行ファイルを読み、読取り不能0・今回の条件に該当する兆候0。認証失敗関連記録も0
継続動作 次の定期確認でも結果時刻が更新。サービス再起動ループなし、関連メールの送信待ち0
メール 「不審な動作の兆候」「ウイルス検査での検出」の表示を確認するテスト2通のSMTP送信成功。件名は通知テストと明記。受信箱到着は受信者が別途確認
ClamAV自己テスト 成功。メモリ上限の実効性・監視継続・テストファイル削除
ClamAV実ファイル検査 2026-10-21 10:26:19 JSTに完了。13,280ファイル、検出0、制限による未完了0。対象範囲での結果

実装時点で、導入前の受信位置まで再集計できた集計区間は9区間でした。当時の基本チェックを新しい解析用の数値データと合わせて確認できないため学習に使える集計区間は0区間で、データ蓄積待ち・本番JSONモデル未適用です。これは実装時の記録であり、読者の環境や将来時点の件数ではありません。

ClamAVの自己テストでは最大メモリ約962MiB・swap 0を確認しました。実ファイル検査は結果ファイルの status=clean・complete=true と検査件数を確認済みです。実ファイル検査の最大メモリは約1,101MiB、最大swapは約36MiBでした。これは指定した対象と制限の範囲で検出がなかった結果であり、Piの全ファイルや感染全般を否定するものではありません。

15-2. 未確認の事項

実ログによるモデル採用・合格モデルの本番配布成功、実攻撃の検出性能、長期の誤検知率、週次の予定時刻での継続実行、PC再起動後の復帰は、上記の単体テストや初回連携だけでは確認できません。認証・実行ファイル監視のテストは、実際の不正ログインやマルウェアを発生させた検証ではありません。プロセスの確認件数は時点で変わり、兆候0を感染なしの証明には使いません。

機械学習の仕組みが実装されていることと、実ログで役に立つモデルが完成していることを分けて記録します。

15-3. 自分の環境で残す記録

日時(JST) 工程・実行場所 操作・確認 結果 未確認・次の対応
記入 OS・SSH/Pi 機種・aarch64・ホスト指紋 記入 記入
記入 Syslog/ルーター・Pi 実ログの到着と保存 記入 記入
記入 Gmail/Pi・受信箱 SMTP受付と受信者の到着確認 記入 記入
記入 学習/コンテナ 候補数・見送り理由・評価条件 記入 記入
記入 更新/Pi モデルID・切替・復元 記入 記入
記入 認証・実行ファイル確認/Pi 最新確認時刻・読取り不能・兆候と自分の操作の照合 記入 記入
記入 ファイル検査/Pi 完了状態・未検査範囲・負荷 記入 記入

この記事の修正作業だけで本番設定を再変更したとは記録しません。公開する記録に、個人のアドレス・認証情報・生ログを含めないでください。

次のステップ

学習用PCの稼働に依存せず運用したい場合は、Docker Hubを経由して学習用コンテナ環境を宅内Ubuntuサーバーへ移す方法があります。リンク先はPi起点の鍵認証rsyncと手動のモデル取得を含む別構成です。本記事のパスワード認証・JSONモデル・評価合格時の自動配布を、そのまま移す手順ではありません。移設する場合は、現行の解析用の数値データ・モデルを採用する条件・JSON形式を維持し、移行先との通信・認証・定期実行を対応させます。
【ネットワークセキュリティ】Pi 4ログ監視の学習環境を宅内Ubuntuサーバーへ移す ~外部非公開・Docker Compose・鍵認証rsync・週次学習~

参考にした記事と反映した考え方

実行環境・データ前処理・時系列評価を確認するための関連記事です。

参考記事のコードを自動実行したり、別プロジェクトのAgent設定をそのまま適用したりする手順ではありません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?