ROAMSWITCH SERVER 1.10.6 ・ 最終更新: 2026-09-26

RoamSwitch Server Edition 運用マニュアル

クラウド VPS(AWS, GCP, Azure, さくらのVPS, ConoHa 等)、データセンター、コンテナホスト環境のための公式導入・設定・運用リファレンスです。

1. 概要 & システム要件

RoamSwitch Server Edition は、常時インターネットに晒される Linux サーバー向けに特化した完全ヘッドレス(GUI 依存ゼロ)の自律型セキュリティ&整合性防護スイートです。

Wi-Fi 接続先の切り替えに応じた動的プロファイル切替を行うクライアント版とは異なり、サーバー版は「インバウンド既定拒否 (Default Drop)」「クリティカルパス改ざん監視 (FIM)」「カーネル LPE (Frag Gap) の予防的封鎖」「eBPF ランタイム脅威監視」「SSH 締め出し防止フェイルセーフ」「Telegram / LINE / Webhook への自律緊急通報」を中核機能とします。

Zero Telemetry 原則(利用者の情報は送らない。既定の外部通信は署名付きデータの受信だけ)
RoamSwitch は、お客様のサーバーのIPアドレス、ポート情報、ファイルハッシュ、システムログ、診断結果を、ベンダーやクラウドへ送信しません。デーモンバイナリ自体には TLS / HTTP クライアントが組み込まれていません。既定で外へ出る通信は、パッケージマネージャーの更新を除くと、署名付きデータの匿名取得(cve_kernel_map_updates_enabled、既定オン)だけです。取得するのは、カーネルCVEデータ、Web ルール、Ubuntu サーバーでは OS パッケージ CVE 照合用の Ubuntu のパッケージCVEデータです。roamswitch-updater が lafine.net から Ed25519 署名つきのデータを HTTPS GET で受信するもので(署名付きマニフェストの確認は毎日、CVEデータ本体は週に1回、Web ルールはマニフェストの版が新しいときだけ)、クエリ文字列と Cookie がなく、ソフトウェアを名乗るヘッダは固定の User-Agent: RoamSwitch-Updater だけで、このホストの情報は一切送りません。roamswitch server config set cve_kernel_map_updates_enabled false(またはセットアップウィザード、サーバー TUI)で止められ、止めても監査は組み込みベースラインで動きますが、Ubuntu サーバーの OS パッケージ CVE 照合は「CVEデータ未取得」になります。それ以外に外へ出る通信は、運用者が明示的に有効にした次のものだけです。通知(Telegram / LINE / Webhook)、npm署名検証(npm_audit_signatures_enabled。コマンド実行時のみ)、自分のドメインの確認(domain_check_domains。ドメインを書いたときだけ。そのドメイン名を、dig がリゾルバーへ、curl が TLD の登録局の RDAP サーバーへ問い合わせます。§3.3)、そしてイベント転送(forward_enabled、既定オフ。§16)。イベント転送は運用者が指定した自組織の宛先(SIEM・syslog・Webhook)にだけ送られ、TLS の syslog はシステムの openssl、構造化 Webhook は curl のサブプロセスで送信します。 このほか、Sensor ペアリング(sensor_pairing_enabled、既定オフ)と、同梱の別ツール accessscope の LLM 連携(既定オフ。コマンドラインでは --llm を指定したときだけ)も、運用者が有効にしたときだけ外部に接続します。例外の一覧は Server Edition セキュリティ設計書の Zero Telemetry の項にあります。

動作要件

2. インストール手順

公式の署名付きパッケージリポジトリを利用することで、依存関係の自動解決とシステム更新コマンドでの安全な自動アップデートが可能になります。

2.1 APT(Ubuntu / Debian / Raspberry Pi OS)

# 1. リポジトリ署名鍵の登録
curl -fsSL https://lafine.net/apt/roamswitch-archive-keyring.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/roamswitch-archive-keyring.gpg

# 2. リポジトリの追加
echo "deb [arch=amd64,arm64 signed-by=/usr/share/keyrings/roamswitch-archive-keyring.gpg] https://lafine.net/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/roamswitch.list

# 3. インストール
sudo apt update && sudo apt install roamswitch-server

2.2 DNF / RPM(Fedora / RHEL / AlmaLinux / Rocky Linux)

# 1. GPG 鍵のインポート
sudo rpm --import https://lafine.net/rpm/RPM-GPG-KEY-roamswitch

# 2. リポジトリ設定ファイルの追加
sudo curl -fsSL -o /etc/yum.repos.d/roamswitch.repo https://lafine.net/rpm/fedora/roamswitch.repo

# 3. インストール
sudo dnf install roamswitch-server

2.3 openSUSE(zypper)

sudo rpm --import https://lafine.net/rpm/RPM-GPG-KEY-roamswitch
sudo zypper addrepo https://lafine.net/rpm/opensuse/roamswitch.repo
sudo zypper refresh && sudo zypper install roamswitch-server

SELinux が有効な Fedora・RHEL・AlmaLinux・Rocky Linux では、パッケージの導入時に、デーモン(/usr/lib/roamswitch/roamswitch-server-daemon)のファイルに initrc_exec_t のラベルを付け、通常の init 起動サービスと同じ initrc_t の権限で動かします。ラベルがないと、デーモンは汎用の init_t で動き、authorized_keys や setuid ファイルの読み取り、カナリアファイルの作成、setpgid が拒否されます。semanage(policycoreutils-python-utils)があれば、ラベルの規則を残すので、再ラベルしても保たれます。なければ chcon で付けるだけなので、再ラベルのあとはパッケージを入れ直してください。パッケージを削除すると、規則も消えます。sudo ps -eZ | grep roamswitch-serv で、デーモンの権限を確かめられます。

パッケージの競合(Conflicts)について
クライアント版(roamswitch)とサーバー版(roamswitch-server)は同一ホストへの重複導入を防ぐため、パッケージマネージャのメタデータで排他指定(Conflicts)されています。サーバー用途では必ず roamswitch-server を指定してください。

3. 初期設定 & サービス確認

3.1 サービス起動状態の確認

インストールが完了すると、roamswitch-server.service が即座に起動し、自動起動(enabled)に設定されます。

sudo systemctl status roamswitch-server.service

初回は、続けて sudo roamswitch server setup を実行してください。それまで、SSH の保守元、通知先、agent_users、ファイルスキャンの対象は未設定です。

パッケージを更新したとき、更新タイマー(roamswitch-update.timer)が無効で、server.conf が更新を切っていなければ、有効にします。更新を切ってある機器では、止めたままにします。

3.2 対話型セットアップウィザード

以下のコマンドで対話型セットアップウィザードを起動し、ポート・保守元 IP・通知先・封じ込め動作を対話形式で構成します(Enter で既定値を採用)。

sudo roamswitch server setup
  1. ポートの指定: SSH メンテナンスポート(既定 22)と公開サービスポート(既定 80,443)をそれぞれカンマ区切りで入力します。
  2. 保守元 IP の指定: SSH 踏み台や監視元の IP / CIDR(例: 203.0.113.50/32)を指定します。ウィザードでは必須で、空欄のまま進めません。全 IP からの SSH を許可する場合は、ANYWHERE と入力します(その場合、隔離中も管理用 SSH が全世界に開いたままになります)。
  3. 緊急通知先の設定: Telegram Bot、LINE Messaging API、汎用 Webhook(Slack / Discord / Teams)をそれぞれ有効化します。
  4. eBPF 重大イベント時の動作: isolate(ホスト隔離)/ freeze(当該プロセス凍結)/ alert_only(通知のみ)を選択。保存後、テスト通知の送信とサービス再起動を確認します。
  5. CVE データの自動更新(既定 y): コンテナ隔離監査が使うカーネルCVEデータベースと Web ルール、Ubuntu サーバーでは OS パッケージ CVE 照合が使う Ubuntu のパッケージCVEデータを、lafine.net から匿名取得(署名付きマニフェストの確認は毎日、CVEデータ本体は週に1回、Web ルールはマニフェストの版が新しいときだけ。HTTPS GET のみ、クエリ文字列・Cookie なし、ソフトウェアを名乗るヘッダは固定の User-Agent: RoamSwitch-Updater のみ、Ed25519 署名を検証)するかを選択します。このホストの情報は送りません。サーバー版で既定で有効な唯一の外部通信で、n にすると止まります(監査自体は組み込みベースラインで動作しますが、Ubuntu サーバーの OS パッケージ CVE 照合は「CVEデータ未取得」になります)。既存の server.conf で無効にしてある場合は、既定の答えも n のままです。
  6. File Scan Guard の設定: メール中継・共有フォルダ・アップロード先のウイルススキャン(§13)を有効化するかどうか、対象ディレクトリを設定します。任意機能で既定は無効です。
  7. Resource Guard の設定: ネットワーク公開サービスの継続的な RSS 増大(メモリリーク/DoS)やクラッシュループを検知するリソース枯渇ガードの有効化を設定します。
  8. 常時稼働の中核保護についての説明表示: インバウンド既定拒否・FIM・eBPF 連携など、ウィザードを介さず常時有効な中核防御機能の一覧が表示されます(設定入力はありません)。
  9. 調査エージェント連携(investigation)の設定: eBPF 重大イベントや、クリティカルパス FIM・依存関係ロックファイル改ざん検知(いずれも通知のみ判定時)について、外部 AI エージェント CLI(Claude Code / agy / Codex CLI / OpenCode、またはカスタムコマンド)に一次調査を自動的に委任するかどうかを設定します(§10.1)。既定は無効です。

ウィザードの次の手順は、質問の前に状況を調べて、足りないものを知らせます。

ウィザードの最後に、確認のコマンドを表示します。サーバー版では roamswitch guards は使えないので、次のコマンドで確かめます。

sudo roamswitch status --server
sudo roamswitch doctor
sudo roamswitch detection-coverage
sudo roamswitch server config show
Telegram Chat ID の注意
スーパーグループの Chat ID は -100 で始まる負の数値です(例 -1001234567890)。curl -s "https://api.telegram.org/bot<token>/getUpdates" のレスポンス中の "chat":{"id":...} で正しい値を確認できます。

3.3 40項目 サーバーセキュリティ診断の実行

sudo roamswitch status --server

現在のファイアウォール状態、Frag Gap 対策、Yama LSM メモリ保護、重要ファイルパーミッション、Docker 露出など 40 項目を一括監査し、0〜100 のセキュリティスコアと評価等級(S〜C)を表示します。 診断には、Docker の daemon.json(user namespace・icc・live-restore・no-new-privileges・TLS なしの TCP 待受け。Docker が無いホストでは対象外)、バックアップの最終実行(7日を超えると不合格。復元できるかは確認しません)、Web サーバーが実際に出している TLS 証明書の期限(30日未満で不合格)も含まれます。名前解決の露出の項目(server_name_resolution_exposure)は、LLMNR(UDP 5355)か NBNS(UDP 137)に答える側として動いていると不合格にします。mDNS(UDP 5353)は表示のみです。

各チェック項目には、コンプライアンス報告に利用できるよう NIST CSF 2.0 のファンクション/カテゴリ(該当する場合は必ず付与)と、確信度の高いマッピングが存在する場合に限り CIS Controls v8 の管理策番号が付与されます。

Webサーバーのセキュリティヘッダーと Cookie: このサーバーのWebサーバー (nginx・Apache・Caddy) のTLSサイトへ、このサーバー自身から トップページ (/) を要求し、返ってくるヘッダーだけを見ます。HSTS (30日以上)、X-Content-Type-Options: nosniff、フレーム埋め込み対策 (X-Frame-Options か CSP の frame-ancestors) の有無と、Cookie の Secure、セッションらしい Cookie の HttpOnly を確かめます。CSP と Referrer-Policy は、内容がアプリごとに違うため判定しません。トップページ以外にだけ付けたヘッダーは見えません。外部へは接続せず、本文と Cookie の値は記録しません。

自分のドメインの SPF・DMARC・有効期限: 任意の確認です。設定 domain_check_domains に自分のドメイン (example.com の形、最大5つ) を書いたときだけ動きます。そのドメインの SPF と DMARC があるか、SPF が誰にでも送信を許していないか、登録の有効期限まで30日以上あるかを調べます。参考として CAA と DNSSEC の有無も表示しますが、不合格にはしません。外部への通信はこの設定のときだけで、次の2つです。(1) dig が /etc/resolv.conf のリゾルバーへ、そのドメインの TXT・CAA・DS を問い合わせます。(2) curl が IANA の公開リスト (ドメイン名は含まれません) で TLD の登録局を調べ、その登録局の RDAP サーバーへドメイン名を送って有効期限を読みます。結果は6時間覚えています。

働いていない保護は、理由つきで表示します。roamswitch detection-coverage、roamswitch doctor、MCP の get_guard_status(effectiveState と inactiveReason)で確かめられます。インストールの状態によるものを除いて、起動の2分後から5分おきに確認し、同じ理由は24時間に1回、緊急通知先に知らせます。

4. ファイアウォール & ポート設定

設定ファイル /etc/roamswitch/server.conf は、機密情報保護のためパーミッション 0600(root のみ読み書き可能)が強制されます。

# --- ファイアウォール ---
ssh_ports=22                     # SSH 管理用ポート(緊急隔離時も維持)
allowed_ports=80,443             # 公開サービス用ポート(TCP のみ。UDP は allowed_udp_ports)
whitelist_ips=203.0.113.10/32    # 保守元 IP / CIDR(空 = 全許可、旧名 admin_source_ips 可)
protect_docker_ports=true        # DOCKER-USER 保護(コンテナの FW 迂回を阻止)
preserve_ssh_on_isolation=true   # 緊急隔離時も SSH 管理経路を温存(false で完全遮断)

# --- eBPF ランタイムガード ---
action_on_critical=isolate       # isolate | freeze(SIGSTOP+ネット遮断) | alert_only
ebpf_socket_path=/run/roamswitch/events.sock

# --- Egress / C2 遮断 & DNS シンクホール ---
egress_ip_blocklist_enabled=true # 既定 true。パッケージ同梱のシードには、JPCERT/CC が公表した攻撃用サーバー3件だけが入っています。ほかはフィードを入れるまで何もブロックしません
metadata_watch_enabled=true      # 既定 true。root 以外のプログラム/コンテナがクラウドのメタデータへ接続したら通知(記録のみ・遮断しない。SSRF の検知)
metadata_watch_allow_users=      # メタデータを正規に使うアカウント(名前または数値ID、カンマ区切り。root は常に対象外)
dns_sinkhole_enabled=false       # 既定 false(社内DNS/split-horizon環境を壊さないため)
dns_sinkhole_provider=quad9      # quad9 | cloudflare | adguard | cleanbrowsing

# --- コンテナ隔離CVEデータ(既定で有効) ---
cve_kernel_map_updates_enabled=true # 既定 true。既定で有効な唯一の外部通信(署名付きのカーネル CVE データ、Web ルール、Ubuntu サーバーでは Ubuntu のパッケージ CVE データを、マニフェストは毎日・CVE データ本体は週に1回、匿名・受信のみ・署名検証で取得)。false で止まる

# --- 通知 ---
telegram_enabled=false
telegram_bot_token=
telegram_chat_id=
line_enabled=false
webhook_enabled=false
webhook_url=
webhook_block_private_addresses=false   # opt-in SSRF guard
webhook_allow_addresses=

# --- システム ---
language=ja
fim_check_interval_secs=300      # 旧名 scan_interval 可
isolation_hard_cap_secs=3600     # 高信頼度の隔離が縮退モードに移るまでの秒数(0 = 解除まで維持)

allowed_ports は TCP のポートだけです。外から来る UDP(WireGuard、DNS・NTP サーバー、HTTP/3 の 443/udp など)は、allowed_udp_ports に書かない限り受け入れません。例は allowed_udp_ports=51820,443 です。既定は空で、確立済みの通信への返信以外の UDP は、すべて破棄します。

forward_* の全キーは、/etc/roamswitch/server.conf.example に載っています。不正な値や、綴りの違うキーは無視されます。起動時のログ、status --server、server config show に警告が出るので、編集したあとに確かめてください。/etc/roamswitch/server.json があると、server.conf は読まれません。一般ユーザーは server.conf(0600)を読めないので、config show は root で実行してください。

設定を直接編集した場合は、サービスを再起動して反映します(roamswitch-server は reload 非対応):

sudo roamswitch server restart

4.1 重大度別ポリシー(guard.yaml、任意設定)

action_on_critical だけでは足りない、より細かい制御が必要な場合は /etc/roamswitch/guard.yaml(配布テンプレート /etc/roamswitch/guard.yaml.example)を作成します。存在しない場合は server.conf の action_on_critical から自動的に等価なポリシーが合成されるため、既存の運用に影響はありません。

policies:
  on_warning:
    action: log_and_notify
  on_critical:
    server_action: isolate_process
    kill_process: true
    fallback_to_host_isolation_after_retries: 3   # 同一プロセスが再発したらホスト隔離へ自動昇格
  on_emergency:
    action: isolate_host_all
    kill_process: true

server_safeguards:
  maintenance_ssh_ports: [22]
  safety_timer_secs: 300   # 低信頼度の隔離を自動復帰させるまでの秒数(0で無効化)。高信頼度の隔離には適用されない(下記)

隔離の自動解除(信頼度別): 固定タイマーだけで解除すると、待つだけの攻撃者に突破されてしまいます。そのため、隔離のトリガーを信頼度で分類します。(1) 低信頼度(eBPF の Error / Warning 以下、汎用のポート異常など): 短いタイマー(safety_timer_secs、既定300秒)の後に全復旧します。(2) 高信頼度(カナリア改ざん、ランサムウェア的な大量書き換え、eBPF の Critical / Emergency / Alert、同一ルールの繰り返しエスカレーション): 黙って復旧しません。短いタイマー以降は原因の再確認を続け、原因の消滅を確認できたときだけ通知付きで解除します。確認できないまま ack もなく上限(server.conf の isolation_hard_cap_secs、既定3600秒、0 = 上限なし)に達すると、縮退(DEGRADED)モードへ移ります。(3) 縮退モード: 保守 SSH ポートとホワイトリスト IP の管理経路以外の新規通信は遮断したままで、時間経過では絶対に自動で開きません。解除は、原因消滅の再確認、明示的な ack(縮退中の ack は解除を意味します)、または sudo roamswitch emergency-restore だけです。どの隔離でも、管理経路(maintenance_ssh_ports と whitelist_ips)以外の確立済み接続(攻撃者がすでに張っている C2 やリバースシェルなど)は遮断されます。各遷移はインシデントタイムラインに記録され、10言語で通知されます。

sudo roamswitch server ack

roamswitch server ack は、FULL 隔離中は隔離を厳格なまま維持して確認応答し(運用者が引き取ったものとして、縮退への移行や自動解除を止めます)、縮退中は解除します。

4.2 Egress / C2 遮断 と DNS シンクホール

egress_ip_blocklist_enabled=true(既定)で、既知の悪性IPへの発信を nftables でブロックします。フィードは /var/lib/roamswitch/threatfeed/malicious_ips.txt(更新機構が管理)があればそれを使い、無ければパッケージ同梱のシード(/usr/share/roamswitch/threatfeed/)を使います(2つを合わせるのではなく、どちらか一方です)。シードに入っているのは、JPCERT/CC が2026年10月15日の報告(Web・API を狙う攻撃)で公表した攻撃用サーバーの3つのアドレス(210.149.87.120・118.26.36.16・69.10.51.162)だけです。貸しサーバーのアドレスは別の人に割り当て直されることがあるため、正規の通信先が止まったときは、このファイルの該当行を確認してください。

dns_sinkhole_enabled=true にすると、フィルタリング DNS リゾルバ(Quad9 / Cloudflare / AdGuard / CleanBrowsing)へ強制的に振り向け、既知の悪性ドメイン解決をブロックします。社内 DNS や split-horizon 構成を壊す可能性があるため既定は false です。

metadata_watch_enabled が有効(既定)なら、nftables テーブル inet roamswitch_metadata_watch が、root 以外のプログラムまたはコンテナがクラウドのメタデータのアドレス(169.254.169.254、169.254.170.2、100.100.100.200、fd00:ec2::254)へ新しく接続したときだけカーネルログに記録し、サーバーデーモンがアカウント(コンテナは送信元アドレス)ごとに 1 日 1 回通知します。記録と通知だけで、通信は遮断しません。攻撃者が自分のサーバーの URL(302 で内部アドレスへ転送する)を渡す手口では、アクセスログには外部 URL しか残らないため、2 段目の接続を見られるのはこの外向き通信だけです。クラウドの権限(ロール)を正規に使うアプリでも通知されるため、metadata_watch_allow_users にアカウント名を入れて除外できます(コンテナは除外不可)。

4.3 着信ポートスキャン検知ガード

短時間に多数の異なるポートへ接続してきた送信元 IP を、nftables のログルールと journald の突き合わせで検知します(既定で有効)。確度が高いスキャン元は一定時間、自動的に遮断できます。RoamSwitch Sensor(§10.1 とは別機能。ペアリング済み Sensor による全ポート監査プローブは誤検知対策として除外されます)と併用する場合、Sensor 側の IP は自動的に対象外となります。

# --- 着信ポートスキャン検知(既定で有効) ---
port_scan_detect_enabled=true              # false で無効化
port_scan_detect_min_distinct_ports=15     # この数以上の異なるポートへの接続でスキャンと判定
port_scan_detect_window_secs=300           # 判定の集計ウィンドウ(秒)
port_scan_block_enabled=true               # 検知したスキャン元IPの自動遮断
port_scan_block_duration_secs=600          # 自動遮断の継続時間(秒)

検知履歴は roamswitch port-scan コマンドで確認できます。

4.4 DNS トンネリング検知ガード

自ホストが送信する DNS トラフィック(UDP/TCP の 53 番ポート)のみを対象に、専用の NFQUEUE 経由で検査します。ラベル長が 40 バイトを超える、シャノンエントロピーが 3.8 を超える、または TXT/NULL レコードの qtype のいずれかに該当する問い合わせを「疑わしい形状」とみなし、直近 60 秒間に 10 件を超えて検知した場合にのみ通知します(同一条件での再通知は 300 秒のクールダウンを設けます)。iodine・dnscat2 など DNS トンネリング/情報持ち出しツールに特徴的な問い合わせパターンの検知が目的です。裏側の nftables ルールには他の DNS 系ガードと同じ bypass フラグが付いており、本ガードが停止・無応答であっても DNS 名前解決が妨げられることは一切ありません。通知専用機能で、自動的な遮断は行いません。他の設定キーと同様、dns_tunnel_detect_enabled の変更はサービス再起動後に反映されます。

# --- DNSトンネリング検知(既定で有効) ---
dns_tunnel_detect_enabled=true              # false で無効化

4.5 SSH 総当たりガード

sshd のログイン失敗を、ログに書かれた瞬間に読み、接続元ごとに数えます(既定で有効)。設定した回数(既定は 600 秒に 6 回)に達した接続元は、nftables の inet roamswitch_sshguard テーブルで、SSH のポートへの新しい接続だけを落とします。最初の禁止は 1 時間で、同じ接続元が繰り返すたびに 4 倍に延び、上限は 7 日です。禁止と同時に、その接続元が SSH にすでに張っていた接続も切ります(接続の追跡エントリ(conntrack)を消し、応答方向のパケットを落とす規則を足して切ります)。禁止の対象は、認証済みのセッションも、公開鍵での成功の記録も、免除も持たない接続元だけで、切る直前にもう一度確かめます。したがって、認証済みのセッションと免除の接続元は切りません。外部への通信はしません(journalctl・nft・loginctl などをローカルで実行するだけです)。

ログは journalctl -f で流し読みします。journald は書き手のプロセスを記録するので、sshd 以外が書いた行は読みません。journald が無い環境では /var/log/auth.log か /var/log/secure を追尾しますが、これらのファイルは syslog を使えるローカルユーザーなら行を偽造でき、第三者を遮断させられるおそれがあります。journald がある環境では、ssh_guard_log_source を既定の auto のままにしてください。数える失敗は、パスワードの失敗、存在しないユーザー、ユーザー名を名乗ったあとの認証前の切断、試行回数の超過、PAM の認証失敗です。公開鍵の失敗(Failed publickey)は数えません。鍵を複数持つ正規のクライアントが、毎回出すためです。接続元のアドレスは、行の末尾にある from <IP> port <N> から読みます。ユーザー名には攻撃者が好きな文字列を入れられますが、そこに偽の接続元を書かれても、第三者は禁止されません。IPv6 は /64 にまとめて数え、禁止も /64 単位です(その /64 にゲートウェイなど守るべきアドレスが含まれるときは、接続元の 1 アドレスだけを禁止します)。

禁止しない相手は、ゲートウェイ、DNS リゾルバー、このサーバー自身、ループバック、whitelist_ips、ssh_guard_allow、認証に成功した SSH セッションがいま開いている接続元、過去 30 日(ssh_guard_trust_key_days)に公開鍵認証で成功した接続元(パスワードでの成功は数えません)、ペアリング済みの Sensor です。これらの接続元が失敗を重ねても、記録だけを残します。プライベートアドレスは免除しません。社内の侵害された端末も、総当たりの発信元になるためです。認証の前の接続が開いているだけの接続元も、免除しません。

fail2ban(sshd の jail が有効なとき)か CrowdSec(firewall bouncer が動いているとき)がすでに sshd を守っている場合は、二重に禁止しないよう、遮断を譲って検知と通知だけを行います(ssh_guard_yield_to_fail2ban、既定は true)。ssh_guard_block_enabled=false のときも、検知と通知だけです。通知するのは、禁止したとき(検知だけのときは、閾値に達したとき)と、失敗を重ねた接続元からログインに成功したとき(重大。ブルートフォースが成功した疑いがあります。セッションは切りません)に限ります。失敗 1 件ごとには通知せず、1 時間に 10 件を超える接続元は 1 通にまとめます。

禁止の一覧は roamswitch server bans(--json あり。一覧は root だけが読めるので sudo を付けます)、解除は sudo roamswitch server unban <IP | IPv6 の /64 | --all> です。解除すると、その接続元の失敗の回数も消えます。禁止は、サーバーを再起動したあとも、残り時間つきで復元されます。roamswitch emergency-restore は SSH の禁止を解除しません(ネットワークを戻すことは、パスワードを推測していた相手を戻す理由にならないためです)。回復メニュー(roamswitch recover)とアンインストールは、テーブルごと消します。MCP の get_ssh_guard_incidents は読み取り専用で、解除のツールはありません。Server TUI では、インシデント画面の「SSH 総当たり」で、禁止の一覧の確認と解除ができます(root で起動したとき。§17)。

roamswitch server bans
sudo roamswitch server unban 203.0.113.9
sudo roamswitch server unban --all

設定キーは 10 個で、すべて ssh_guard_ で始まります(§18.1)。たとえば sudo roamswitch server config set ssh_guard_max_failures 10 のように変更し、反映には sudo roamswitch server restart が必要です。ガード全体を止めるには ssh_guard_enabled=false、検知と通知だけにするには ssh_guard_block_enabled=false にします。診断(40 項目)の「SSH ブルートフォース防御」は、このガードが動いていて禁止する設定のときも合格になります。

限界: 公開鍵の失敗だけを重ねる試行は数えません。多数の接続元に分散した総当たりは、接続元ごとの回数で数えるため、1 つの接続元が閾値に達するまでは止まりません。根本的な対策は、パスワード認証を無効にして、公開鍵認証だけにすることです。

4.6 起動時の安全装置(xdp-gate)

sudo roamswitch xdp-gate enable は、起動の直後から、ファイアウォールが入るまでの間を守るゲートを有効にします。eBPF オブジェクトがないと、何も変えずに止まり、理由を表示します。標準のパッケージには、このオブジェクトは入っていません。

有効にするときは、安全装置のタイマーを先に、ゲートを後に有効にします。失敗したときは戻します。効くのは次回の起動からで、タイマーが有効なときだけ、起動の60秒後にゲートを解放します。状態は sudo roamswitch xdp-gate status で確かめられます。

5. SSH 締め出し防止フェイルセーフ

リモートサーバーの運用で最も重大な事故は「ファイアウォール誤設定による SSH ロックアウト」です。RoamSwitch Server Edition は多重の防御策を備えています:

6. コンテナ保護 (Docker / Podman)

Docker は標準でコンテナのポート公開時(-p 8080:8080 等)に独自の iptables ルールを作成し、ホストの標準ファイアウォールをバイパス(貫通)して全世界へ直接ポートを露出させる既知の問題があります。

protect_docker_ports=true(既定)のとき、RoamSwitch は Docker のファイアウォール方式に合わせて、コンテナ宛てのトラフィックも ssh_ports と allowed_ports に含まれるポート宛て以外は外部から届かないようにします。iptables / iptables-nft 方式では DOCKER-USER チェーンの先頭に検査ルールを挿入します(IPv4 に加え、Docker の IPv6 が有効なら ip6tables でも)。Docker 29 以降のネイティブ nftables 方式(DOCKER-USER が存在しない)では、専用の inet roamswitch_docker_guard テーブルを Docker のチェーンより前の forward フックに作ります(1つの inet テーブルで IPv4 / IPv6 の両方を扱います)。判定は DNAT 前の元の宛先ポート(ctorigdstport)で行い、外部インターフェースからの通信だけを対象にするため、コンテナからの外向き通信やコンテナ間通信には影響しません。意図しない開発用データベースコンテナ等の外部露出を防ぎます。

このガードは冪等で、1分ごとに自己修復します。dockerd が RoamSwitch より後に起動した場合、Docker の再起動やリロードでルールが書き換えられた場合、手動の nft flush や nftables テーブルの消失があった場合にも、現在有効なガードには手を加えないまま再設定します。

あわせて、docker events を監視するリアルタイムガードが、--privileged で起動したコンテナや /var/run/docker.sock がマウントされたコンテナ(コンテナ脱出リスク)を起動直後に検知して通知します。さらにコンテナ exec ガード(container_exec_guard_enabled、既定オン)が、コンテナ内でのバイナリの実行を YARA で検査します(実行後にファイルを削除する回避も、開いたファイル記述子から内容を読むため検査できます)。このガードはすでに稼働中のコンテナにも起動時にマークを付け、新規コンテナにも自動で追随します。どちらも通知のみで、自動遮断は行いません。Docker 29 でも動きます(1.11.1 で対応)。イベントの購読が続けて失敗するときは、再接続の間隔を5秒から最大5分まで広げます。

7. クリティカルパス FIM (改ざん監視)

150 以上の重要ファイル(/bin/login, /usr/bin/sudo, /usr/sbin/sshd 等の重要バイナリ、/etc/shadow, /etc/sudoers 等の認証設定、systemd ユニット)を SHA-256 で監視します。

監視範囲は個別のファイルだけではありません。重要ディレクトリ(/etc/pam.d、/etc/sudoers.d、/etc/ssh/sshd_config.d、/etc/cron.*、/etc/systemd/system、/etc/ld.so.conf.d、/etc/profile.d、/root/.ssh(known_hosts を除く))では新しいファイルが現れたことも検知し、ログインできる全アカウントの authorized_keys、/etc/ld.so.preload(存在しないのが正常で、出現はローカルルートキットの兆候)、RoamSwitch 自身のバイナリも対象です。snapd が自動生成する一時ユニット(snap-*.mount 等)は対象外です。新規ファイル検知はこの範囲を含むベースラインでのみ有効になるため、アップグレード後は一度 sudo roamswitch fim update を実行してください。root 以外で fim verify を実行して読めないファイルは、改ざんではなく「読み取り不可としてスキップ」として扱います。

7.1 手動整合性検証

sudo roamswitch fim verify

7.2 イベント駆動検知(即時)

監視対象ファイルは fanotify(FAN_CLASS_NOTIF、書き込み完了イベント)で個別に監視されており、次回の周期スキャンを待たず、改ざんが発生した瞬間にハッシュを再照合します。周期スキャン(既定 fim_check_interval_secs=300 秒間隔)はイベントを取りこぼした場合のバックストップとして併存します。新しいファイルを同じ名前の下に置く編集(install・sed -i など)も検知します。通知には、変更したプロセスと SSH ログインを添えます(§7.5)。

7.3 OS パッケージ更新時の自動ベースライン同期

Debian / Ubuntu 環境では、パッケージインストール時に /etc/apt/apt.conf.d/99roamswitch-fim の DPkg::Post-Invoke フックが自動構成されます。Fedora / RHEL / Rocky / AlmaLinux / openSUSE では、/var/lib/rpm を監視する systemd.path ユニット(roamswitch-fim-update.path)が同等の役割を果たします。

これにより、管理者が sudo apt upgrade(または dnf upgrade / zypper update)を実行してバイナリが正規に更新された際は、RoamSwitch が自動で検知してハッシュベースラインを静かに再同期します(誤報アラートの発生を防止)。手動で更新を反映する場合は以下を実行します:

sudo roamswitch fim update

デーモンが初めて起動してベースラインを作るとき、パッケージ管理(apt・dpkg、dnf・rpm、zypper、pacman)が作業中であれば、終わるまで最大120秒待ってから作ります。Apache などと同じ apt install で導入しても、dpkg が作業中の一時ファイル(*.dpkg-new など)がベースラインに入り、直後の検証で改ざんに見えることはありません(1.11.18 以降)。120秒たっても終わらないときは、警告を記録して、そのまま作ります。

7.4 ハニートークン(認証情報デコイ)

デーモン起動時、root 権限で動作する特性を利用して /root 配下に .aws/credentials、.ssh/id_rsa、.docker/config.json の3種のおとり認証情報ファイル(ハニートークン)を冪等に配置します。該当パスに実ファイルが既に存在する場合は絶対に上書きしません。バックグラウンドの監視プロセスが inotify でこれらのファイルへの読み取りアクセスを検知すると、侵入後の認証情報探索(MITRE ATT&CK T1552: Unsecured Credentials)とみなし、重大度 Critical の通知とインシデントタイムライン記録を即座に行います。既定で有効で、設定キー honeytokens_enabled(false にすると配置と監視を止めます。すでに置いたおとりは残ります)と honeytokens_home(配置先。既定は /root)で変えられます。1.10.4 までの配布パッケージには、この機能の実行に必要な roamswitch-honeytokens が同梱されておらず、パッケージから入れた環境では動作していませんでした。1.10.5 から同梱され、サーバー版で systemd のサンドボックスが /root への書き込みを止めていた問題は 1.10.10 で直りました。1.11.1 から、RoamSwitch 自身が実行する docker のコマンドは、空の専用ディレクトリ(root では /run/roamswitch/docker-config)を DOCKER_CONFIG に指定して起動するため、おとりの .docker/config.json には反応しません(それまでは、デーモンの監視や roamswitch status --server が、おとりを読んで誤って検知することがありました)。ほかのプログラムが読めば、これまでどおり検知します。 server.conf の agent_users(§20)に挙げたアカウントのホームにも、同じおとりを置き、アカウントごとに監視を 1 つ起動します。

いずれかのハニートークンに実アクセスがあった場合、攻撃者がさらに深く探索してくることを見越し、文脈的に関連する新規のおとり(例: .aws/credentials への到達後は .aws/config、.ssh/id_rsa への到達後は .ssh/config、.docker/config.json への到達後は .kube/config)を1トリガーあたり最大2件、全体で最大6件まで追加配置します。侵害調査中に /root 配下でこれらの見慣れないファイルが増えていても、これは想定内の挙動です。

7.5 侵入経路(誰が、どの SSH ログインから変更したか)

ファイルの変更を知らせる通知(クリティカルパス FIM とロックファイル FIM)に、変更したプロセス(アカウント・実行ファイル・コマンド)と、そのプロセスが属するSSH ログイン(接続元の IP とポート・ログイン時刻・認証方法・公開鍵の指紋)を添えます(既定で有効。fim_intrusion_context_enabled)。イベントを読んだその場で、書き込んだプロセスの /proc/<pid>(実行ファイル、ユーザー、loginuid、sessionid、cgroup、親の連鎖)を撮ります。tee や sed のように一瞬で終わるプロセスのためです。すでに終了していて撮れなかったプロセスは、実行レコーダー(有効なとき)の記録で補います。ログインへの結び付けは、SSH ガードのセッション表を先に使い、なければ logind と journald の Accepted の行から引きます。SSH ガードを無効にしていても動きます。外部には何も送りません。

信じるのは、カーネルが報告する値だけです。実行ファイルは /proc/<pid>/exe(argv[0] ではありません)、ログインは loginuid(ログイン時に一度だけ設定され、sudo や su を越えて引き継がれます)と cgroup の session-N.scope、それに親の連鎖です。comm・コマンドライン・環境変数のように、プロセス自身が変えられる値は、表示するだけで判定には使いません。sshd を名乗るだけのプロセスは、SSH のログインとは見なされません。通知は、次の三つを区別して書きます。見つかった(接続元・時刻・認証方法・鍵)、「SSH のログインは見つかりませんでした」(調べた結果、systemd のサービス、コンテナ、ローカルのログインセッション、ログインセッションに属さないプロセス、のどれかだったとき)、「ログインの経路は調べられませんでした」(プロセスがすでに無く実行レコーダーにも無い、logind が答えない、定期検査で見つけた変更でプロセスが分からない、など)。調べられなかったことを、ログインではないとは書きません。取得に失敗しても通知は必ず出ます。

/etc/ssh/sshd_config: modified
  プロセス: root(ログインしたアカウント: admin) / /usr/bin/tee / コマンド: tee /etc/ssh/sshd_config (pid 4121)
  SSH ログイン: 203.0.113.25:51544 から admin が 2026-10-17 03:12:08 にログインしたセッションです(認証: publickey, 鍵 SHA256:AbCdEf0123456789)

/etc/sudoers: modified
  ログインの経路は調べられませんでした(定期検査で見つけた変更のため、変更したプロセスは分かりません)

install・sed -i・同じディレクトリでの cp と mv のように、新しいファイルを同じ名前の下に置く編集も検知します。ファイル単位の印は inode に付くため、以前はこの種の編集でイベントが出ませんでした。いまは、監視対象のファイルがあるディレクトリにも印を付け、同じディレクトリで別の名前のファイルが閉じられると、数ミリ秒後に名前を引き直します。限界もあります。別のディレクトリで書いたファイルを mv で持ち込む場合は、周期スキャン(fim_check_interval_secs)が見つけるまで分かりません。そのときは書き込んだプロセスが分からず、通知にもそう書かれます。

記録は、通知のほかに、インシデントタイムライン(種別 fim)と /var/lib/roamswitch/fim_intrusions.json(root 専用、最新 100 件)に残ります。MCP の verify_fim には、追加のフィールド recentIntrusionContext として出ます(root で動かしたときだけ)。改ざんかどうかの判定は変わりません。限界もあります。実行レコーダーを無効にしていて、書き込んだプロセスが極端に短命なときは、実行ファイルが分からず、親プロセスの実行ファイルとコマンドラインを出します。持続化の監視(persistence_watch)の通知にはプロセスの情報がないので、侵入経路は付きません。侵入経路が示すのは、変更したプロセスとログインまでです。侵入の入口そのもの(突かれた脆弱性や、鍵が盗まれた経緯)は分かりません。止めるには fim_intrusion_context_enabled=false にします。

7.6 SUID / SGID ファイルの棚卸し

SUID が付いた実行ファイルは、実行した人ではなく持ち主(多くは root)の権限で動きます。攻撃者は、root を保つために、自分のプログラムや shell の複製に SUID を付けます。クリティカルパス FIM は固定の一覧しか見ないので、一覧にない場所の新しい SUID ファイルや、既存のファイルに後から付いた SUID は見えません。そこで、ローカルのファイルシステムを suid_watch_interval_secs(既定 600 秒)ごとに歩き、SUID または SGID が付いた実行ファイルの、モード・所有者・サイズ・SHA-256 を記録します(既定で有効。suid_watch_enabled)。最初の一覧(/var/lib/roamswitch/suid_baseline.json)を信頼する状態とし、以降は、新しいファイル、後から付いた SUID、モードや所有者の変更、内容の変更を、Critical として 1 回だけ通知します(ATT&CK T1548.001)。通知の各行に、パス・モード・所有者の UID・属するパッケージを出します。

パッケージの更新による変化は、通知せずに基準に取り込みます。ファイルがパッケージ(dpkg -S、rpm -qf、pacman -Qo、apk info -W)に属し、かつ変更時刻(ctime)が、パッケージデータベースの最新の更新より後でないときです。更新のあとの chmod u+s は、ctime がそれより後になるので通知されます。確認は sudo roamswitch fim verify、現在の状態を受け入れるのは sudo roamswitch fim update です。

限界: コンテナのイメージ層(/var/lib/docker、/var/lib/containerd、/var/lib/containers、/var/lib/kubelet、/var/lib/lxc、/var/lib/lxd、/var/lib/incus)、snap、ネットワークや FUSE・overlay・squashfs のマウントは歩きません。歩きの間にビットを付けて外す操作と、パッケージマネージャが動いている間の変化(最大 3 回、延期します)は見えません。歩きが 300 秒で終わらなければ、その回は捨てます。

7.7 ログの消去の検知

ログの消去は、ほとんどの侵入の最後の動きです(ATT&CK T1070.002)。ログは本来、変わり続けるファイルなので、FIM では見えません。30 秒ごとに、auth.log・secure・syslog・messages・kern.log・audit/audit.log・cron・dpkg.log・wtmp・btmp・lastlog の inode とサイズを覚え、小さくなった(1024 バイト以上だったもの)、消えた、別のファイルに入れ替わったものを、Critical として通知します(既定で有効。log_tamper_watch_enabled)。ジャーナルは、アーカイブされたファイル(system@….journal)の合計が、4 MiB 以上あったものから 5 分の 1 未満になったときに通知します(journalctl --vacuum-time=1s など)。

ログローテーションによる変化は除きます。同じディレクトリに同じ名前で始まる別のファイルがあり、inode が以前のファイルと同じ(名前の変更)か、変更時刻が直近 120 秒以内(copytruncate の複製、圧縮されたファイル)のとき、または logrotate の状態ファイルが直近 120 秒以内に更新されているときです。限界: wtmp・btmp・lastlog の記録を、サイズを変えずに書き換える操作と、空にしてから元のサイズまで書き戻す操作、デーモンが動いていない間の変化は見えません。

8. eBPF ランタイムガード & Falco / Tetragon 連携

カーネル空間で動作する eBPF プローブと連携し、カーネル特権昇格脆弱性(Frag Gap)やリバースシェル、コンテナ脱出を検知します。既定のセンサーは Falco(UNIX ドメインソケット経由の JSONL)です。guard.yaml の sensor.type: tetragon を選択すると、Cilium Tetragon をセンサーとして使うこともできます(Tetragon 側のネイティブ gRPC API を同じ UNIX ソケット越しに購読するのみで、新規リッスンポートは増えません)。

8.1 ログ肥大化・I/O 負荷の徹底排除

従来のセキュリティツールで問題となる「syslog の爆発的肥大化」「ディスク容量圧迫」を防ぐため、RoamSwitch は Falco と UNIX ドメインソケット /run/roamswitch/events.sock で直結します。このソケットは root 専用(0600)で作成され、書き込めるのは root で動く Falco だけです(追加の権限設定は不要です)。

8.2 自律リアクション(検知即プロセス凍結)

悪性コードの実行や不審通信を検知した瞬間、デーモンは該当プロセスの PID へ即座に SIGSTOP を発行して実行を凍結し、nftables ルールで通信を遮断(Air-Gap)します。

8.3 侵害証拠の自動保全(Forensic Evidence Capture)

eBPF ランタイムガードがプロセス単位の封じ込め(SIGSTOP による凍結 + ネットワーク遮断)を実行した際に、自動的に証拠保全バンドルを作成します(§4.1 の既定ポリシーでは Critical 相当のイベントがこの経路に該当します。ホスト全体を隔離する Emergency 相当のイベントでは、本バンドルは現時点では作成されません)。バンドルの内容は、既定のルーティングインターフェース上でのベストエフォートな約30秒間の境界付きパケットキャプチャ(tshark 未導入の環境ではこの項目のみ静かにスキップされ、その旨が記録されます)と、対象 PID が判明している場合の /proc/<pid> スナップショット(status、cmdline、maps、environ(KEY/TOKEN/SECRET/PASSWORD を含む名前の値は [REDACTED] に置換)、オープン中のファイルディスクリプタのリンク先一覧、PID 1 までの祖先プロセスチェーン)です。すべての取得済みファイルの SHA-256 ハッシュを記録した manifest.json が同梱され、後日の改ざん検証に利用できます。 強制終了を伴う封じ込め(リバースシェル検知など)では、/proc のスナップショットを強制終了の前に同期で保存し(最長 3 秒)、終了後のバンドルはパケット取得だけを担当します。--pid を指定した手動のスナップショットも、プロセスの情報を先に保存してからパケット取得を行います(--no-packets でパケット取得を省けます)。

保存先は /var/lib/roamswitch/incident-evidence/<タイムスタンプ>-<ID>/ です。バンドルは自動削除されません。ディスク圧迫を防ぐため、保持期間(既定 30 日)を超えた古いバンドルを明示的に削除するには次を実行します:

sudo roamswitch-incident-capture prune [--keep-days N]

また、封じ込めの発動を待たず、任意のタイミングで手動のスナップショットを取得することもできます:

sudo roamswitch-incident-capture snapshot [--pid PID] [--reason TEXT] [--json]

8.4 隔離の速さの計測と、警報経路のハートビート

eBPF による隔離の精度と速さは、センサー(Falco や Tetragon)のルールに左右されます。実際、Falco 0.45.0 の標準ルール(29 件)には、ホスト上のリバースシェルを検知するものがありません。リバースシェル向けのルールはコンテナ専用で、一部は RoamSwitch の priority: warning の条件で捨てられます。RoamSwitch は、ルールの出来を前提にせず、次の三つで補います。隔離にかかった時間を測ること、警報の経路が生きていることを定期的に確かめること、リバースシェルを自分でも検知することです。

隔離の遅延の計測として、eBPF のインシデントごとに、Falco が検知してからデーモンが警報を読むまで(detect_to_receive_ms)、読んでから対処が終わるまで(receive_to_action_ms。隔離の呼び出しが戻ったあとに測ります。ホスト Air-Gap は、ルールセットを読み込み終えるまで)、その合計(detect_to_action_ms)を記録します。値は ebpf_incidents.json と、MCP の get_ebpf_incidents に、追加のフィールドとして出ます。プロセスを隔離したときの通知には、「検知から対処まで N ミリ秒」の 1 行が付きます。実測(Ubuntu 24.04 の VM、Falco 0.45.0、modern eBPF)は、検知から受信まで 1〜2 ミリ秒、受信からプロセス隔離と SIGKILL まで 41〜62 ミリ秒(合計 42〜64 ミリ秒)、ホスト Air-Gap は合計 109 ミリ秒でした(目標の 150 ミリ秒の内側)。値はこの VM のものです。Tetragon は受信時に時刻を付けるので、detect_to_receive_ms はありません。

ハートビート(ebpf_heartbeat_interval_secs。既定は 600 秒、0 で無効、有効にするときは 60〜86400)は次のように働きます。パッケージには、RoamSwitch 自身の Falco ルール 96-roamswitch-heartbeat.yaml が入っています。デーモンは定期的に、root 専用の使い捨てのパス(/run/roamswitch/heartbeat/ の下の乱数)を開き、そのルールの警報が Falco の出力から戻ってくるまでを測ります。1 回の往復で、ルールが読み込まれていること、Falco が動いていること、出力の経路が生きていることを、まとめて確かめられます。戻らない確認が 2 回続いたとき(1 回目の 1 分後に再確認)だけ、「ルールが読み込まれていない、または出力が届いていない」という警告を、24 時間に 1 回まで通知します。ハートビートの行は、隔離・通知・許可リスト・インシデント記録のどれにも通さずに捨てます。外部には何も送りません。実測の往復は 2〜6 ミリ秒で、ルールを外すと約 164 秒後に警告が出て、戻すと約 75 秒で正常に戻りました。

状態は /var/lib/roamswitch/ebpf_heartbeat.json に残り、MCP の get_ebpf_incidents の heartbeat と、診断 K-04(eBPF ランタイムエージェント。診断の項目数は 40 のままです)が読みます。Falco が動いていても、ハートビートが戻らなければ K-04 は不合格です。限界もあります。確認するのは 1 つのルール(open 系のシステムコール)の往復で、ほかのルールが個別に壊れていることまでは分かりません。確認の間隔(既定は 10 分)のあいだに壊れたものは、次の確認まで分かりません。Tetragon では行いません。Falco が動いていない、またはルールのファイルが無いホストでは、何も確認せず、何も警告しません(停止は従来の通知が担当します)。Falco は確認のたびに警告を 1 行、syslog に書きます(既定なら 1 日 144 行)。

8.5 リバースシェル検知(RoamSwitch 自身)

Falco のルールに頼らず、RoamSwitch 自身が 2 秒ごとに /proc を読み、標準入力と標準出力が、このホストから外部のアドレスへ張った 1 本の TCP 接続につながっているシェル・インタプリタ(sh・bash・python・perl・ruby・php・node など)を探します。bash -i >& /dev/tcp/…、python、perl、nc -e、socat exec: が対象です。2 回続けて見えたものだけを、プロセスごとに 1 回報告します。既定は外部の公開アドレス宛だけで、プライベートアドレス宛(RFC 1918・CGNAT・リンクローカル)は reverse_shell_watch_include_private=true のときだけ数えます(ssh の ProxyCommand、コンテナ、ローカルの開発に正規のものが多いためです)。ループバックは見ません。外から受け付けた接続(inetd や socat TCP-LISTEN が accept したもの)も対象外です。実測(Ubuntu 24.04 の VM)では、bash の /dev/tcp、python、perl、nc -e、socat exec: を、接続から約 2.4 秒で検知しました。ssh、ProxyCommand 付きの ssh、git over ssh、nc のクライアントは検知しません。

検知は、RoamSwitch Reverse Shell という Critical の eBPF イベントとして、Falco の警報と同じ経路(許可リスト、通知のクールダウン、guard.yaml の重大度ごとの動作、インシデント記録)に入ります。既定では、プロセスの隔離と終了(kill)を行います。root が所有するリバースシェルは、プロセス単位の隔離に失敗するため、既存の Critical の方針どおり、ホスト全体の Air-Gap まで進みます。正規のプログラムが誤って検知されるのが心配なときの逃げ道は二つあります。reverse_shell_watch_notify_only=true(Warning として通知だけを行い、隔離も kill もしません。既定は false)と、許可リストへの登録 sudo roamswitch server allowlist add "RoamSwitch Reverse Shell" <プロセス名> です。設定キーは reverse_shell_watch_enabled(既定 true)、reverse_shell_watch_include_private(既定 false)、reverse_shell_watch_notify_only(既定 false)の 3 つです。

組み込みの一覧(init、コンテナランタイム、sshd、パッケージ管理、RoamSwitch 自身など)に加えて、server.conf の protected_processes(カンマ区切り)に書いたプロセスは、ガードが SIGSTOP も SIGKILL もしません。eBPF ガードは、そのプロセスを隔離せず、記録と通知だけにします。プロセス名は、実行ファイルがシステムの場所(/usr/、/bin/、/sbin/、/lib/、/opt/、/snap/、/nix/store/ など)にあるときだけ有効で、/tmp に置いた同名の偽物は守られません。絶対パスは、その実行ファイルだけに一致します(システムの場所の外に入れたソフト向け)。実行ファイルのパスを読めないときは、一覧にある名前を守る側に倒します。不正な値は保存されません(名前は英数字と _ - . + @、パスは .. と空白を含まない絶対パス)。roamswitch server allowlist test <rule> <process> [--exe パス] は、何も変更せずに、許可リスト(誤検知の許可。期限つき)にあるか、凍結・kill から保護されているかを表示します。サーバー TUI のツールからも実行できます。 server.conf の protected_processes で設定します。反映には再起動が必要です(sudo roamswitch server restart)。システムの場所の外に入れたソフトは、名前ではなく絶対パスで書いてください。パスで指定したファイルは書き換えられても守られたままになるので、root 所有にしておいてください。

server allowlist の項目には期限があります。期限が切れた項目は、ガードから見て、なかったものとして扱われます。一覧には「期限切れ: 許可は効いていません」として残り、更新か削除までそのままです。期限の仕組みが入る前の項目は効き続け、「期限なし(古い項目)」と表示され、更新か削除までの間、1日1回通知されます。Sensor 同居のプリセットには期限がなく、プリセットを切ると消えます。Server デーモンが5分ごとに確認し、設定した通知先へ、期限の7日前に1回、期限切れのときに1回、通知します。時計は、壁時計を、再起動をまたいで残る印(/var/lib/roamswitch/allowlist_clock、書き込みは最大5分に1回)まで引き上げたものなので、時計を戻しても期限切れの項目は戻りません。これは設定の間違いや時計のずれへの備えで、root を持ち印のファイルを書き換えられる攻撃者への備えではありません。項目を足したり更新したりするのは人だけです(CLI と Server TUI)。ガードも MCP ツールも行いません。遮断(BAN・隔離)には期限を付けません。Server TUI の許可リスト画面は各項目の期限を表示し、e キーで選んだ項目を30日延ばします。TUI で足した項目は30日です。Linux クライアントのデーモンも同じ判定を使うので、期限はそこでも効きます。期限の通知は Server デーモンだけが出します。

限界もあります。名前(プロセス名と実行ファイル名)で絞るので、シェルやインタプリタ以外の名前で、標準入出力をソケットにつないだ自作のバイナリは見つかりません。ループバック経由(ssh -R の折り返しなど)の接続と、2 秒より短い接続は見ません。ホストのプロセス空間から見えないプロセスも見られません。Linux のクライアント版にも同じ検知があり、そちらは通知だけを行います。

標準入力と標準出力が接続につながっていない形も、2 つ見ます。1 つは、インタプリタ(python・node など)や openssl s_client・telnet・ssh・nc・socat が外向きの接続を持ち、-c を付けずに起動した対話シェルが、パイプ、/tmp などの FIFO、または自分が持つ pty(一行コマンドの場合)を通じて、その接続と双方向につながっている形です。もう 1 つは、TCP の代わりに、接続済みの UDP ソケットを使う形です。前者は、待ち受けもしているプロセス(開発サーバー、IDE のリモートサーバー、ノートブック)を対象外にします。前者の形は、イベントが Warning になり、guard.yaml の設定にかかわらず、隔離も kill もホスト隔離への段階上げも行わず、通知と記録だけです。UDP の形は、TCP と同じ Critical として扱います。

9. 緊急アラート通知 (Telegram / LINE / Webhook)

重大なインシデント(ポート不正露出、FIM 改ざん、eBPF 脅威検知、緊急隔離発動)が発生した際、外部の通知プラットフォームへ即座に状況を配信します。

[notifications]
language = ja
# Telegram: Bot Token と Chat ID
telegram_bot_token = "123456789:ABCdefGHIjklMNOpqrSTUvwxYZ"
telegram_chat_id = "-1001234567890"

# LINE: Messaging API チャネルアクセストークン と ユーザーID
line_channel_access_token = "YOUR_LINE_ACCESS_TOKEN"
line_user_id = "U1234567890abcdef1234567890abcdef"

# 汎用 Webhook (Slack, Discord, Teams, 自社監視基盤)
webhook_url = "https://hooks.slack.com/services/T00/B00/XXXXX"

これらの外部通知先を設定していない場合でも、送信された通知は直近1週間ぶんローカルに保持されます。roamswitch notificationsで確認できます。

通知先がない、または有効でも token・宛先・URL が空のときは、緊急の検知は誰にも届きません。記録は、journalctl と roamswitch notifications にだけ残ります。経路が1つもないと、診断の「緊急通知の経路」の項目が不合格になり、点数が下がります。通知を使わず、Server TUI だけで運用する場合も同じです。

roamswitch notifications は、出した通知の記録です。届いたかどうかは示しません。届くかどうかは、sudo roamswitch server test-notify で確かめます。設定済みのチャネルに、実際のテストメッセージを送ります。

通知(Telegram・LINE・Webhook)が届いたかは、HTTP の状態で判定します。2xx は送信済み、408・429・5xx と接続の失敗は再試行、それ以外の 4xx(トークンの誤りなど)は再試行しません。再試行は、すぐに 3 回試します(間隔は 1 秒、5 秒)。それでも届かないときは、/var/lib/roamswitch/pending_deliveries.json(root 所有、0600、最大 50 件)に入れ、5 分おきに 1 件ずつ再送します。24 時間たつか、24 回試しても届かなければ、あきらめてログに残します。再送する通知の文面の先頭には「【遅延送信】」が付きます。Air-Gap の直前の緊急通知は 1 回だけ試し、届かなければキューに入れて、接続が戻ったら送ります。ログには、トークン・Webhook の URL の秘密(user:pass@)・Authorization を出しません。

保護機能が、読むべきログ・テーブル・イベントを 2 分以上読めなかったときは「保護機能が入力を読み取れていません」、ログの切り替わり・カーソルの消失・長すぎる行・キューの溢れ・読み取りの失敗などで取りこぼしが記録されたときは「入力に取りこぼしの可能性」という通知が、上の通知先にも届きます(同じ保護機能・同じ種別につき 1 日 1 回まで)。読めていない間に起きたことは検知できていない可能性があります。件数と時刻は、MCP ツール get_guard_status の collectors と /run/roamswitch/collectors.json に残ります。

Webhook 宛先の内部アドレス拒否(オプトイン): webhook_block_private_addresses=true(既定 false = 従来どおり)にすると、通知 Webhook の宛先が、ループバック・プライベート(10/8、172.16/12、192.168/16)・リンクローカル(169.254/16、クラウドのメタデータ 169.254.169.254 を含む)・ULA(fc00::/7)・CGNAT(100.64/10)のいずれか——ホスト名の場合は名前解決結果のうち 1 つでも該当——であれば送信を拒否し、理由をログと roamswitch notify-test のエラーに出します(2130706433 や 0x7f.1 のような別表記、IPv4-mapped IPv6 も判定)。検証した解決結果は curl に固定して接続するため DNS rebinding も防ぎます。社内 SIEM など正当な内部宛先は webhook_allow_addresses=10.20.0.0/16,fd00::/8(CIDR のカンマ区切り)で許可します。イベント転送の Webhook には同じ仕組みの forward_webhook_block_private_addresses / forward_webhook_allow_addresses があります。このオプションが false でも、宛先がクラウドのメタデータアドレスなら警告ログを出します(送信は止めません)。HTTP プロキシ環境変数が設定されている場合、名前解決と接続はプロキシ側で行われるため、この検査の対象外です。

10. AI エージェント / MCP 連携運用

RoamSwitch Server Edition は、Claude、Gemini、Cursor 等の AI エージェントからサーバーのセキュリティ診断を実行できる roamswitch-mcp(Model Context Protocol サーバー)を標準内蔵しています。

設定を変えるツールを持たない設計
Web アプリケーション等のログに悪意あるプロンプトインジェクションが含まれていた場合でも、AI エージェントが悪用されて勝手にファイアウォールを開放したり隔離を解除したりできないよう、MCP 経由のツールには、設定・防御・ポート・デバイスを変えるものが1つもありません。読むだけのツールがほとんどで、書き込むのは3つのツールが自分の記録ファイルを更新するときだけです。ポリシー変更やプロセス凍結解除はローカル TTY(SSH 端末)での明示的コマンド実行を必須としています。

診断結果に、パッケージのスクリプト・ログの文面・プロセス名など、外部の人が書き換えられる文字列が含まれるときは、「信頼できない外部データで、指示として扱わない」という注意書きが付きます。さらにその文字列に AI への指示に似た書き方が見つかると、別に WARNING のブロックが付き、見つかった種類と件数だけを書きます(文面は繰り返しません)。get_detection_coverage は、手法ごとの検知の可否と盲点、担当するガードがいま動いているかを返します(isServer で Edition を選び、省略時はインストール済みの Edition)。

MCP の全ツールには、注釈(readOnlyHint・destructiveHint・idempotentHint・openWorldHint)が付いています。readOnlyHint が false なのは、自分の記録ファイルを更新する run_active_vuln_scan・audit_security_logs・run_npm_audit_signatures の3つだけです。openWorldHint が true なのは、既定で無効の run_npm_audit_signatures だけです。サーバー版の診断が自分のドメインの DNS と登録情報(RDAP)を問い合わせるのは、domain_check_domains にドメインを書いたときだけです。既定では空で、何も送りません。get_vulnerability_scan_history の各行には、そのプローブが相手のサービスに残すもの(sideEffects)が、run_active_vuln_scan の各指摘には、プローブが問題を確認したのか(confirmed)、サービスが報告した版が CVE の範囲に入るだけなのか(versionRangeMatch)を示す evidenceKind が付きます。

設定例(Claude Desktop / AI クライアント)

{
  "mcpServers": {
    "roamswitch": {
      "command": "/usr/bin/roamswitch-mcp",
      "args": []
    }
  }
}

10.1 調査エージェント連携(investigation、一次自動調査のAIエージェント委任)

§10 の roamswitch-mcp が「人間の AI クライアントからの問い合わせ」向けなのに対し、investigation 連携はデーモン自身が検知イベントをきっかけに外部 AI エージェント CLI を自律的に起動する、別の仕組みです。対象は、通知のみ判定(自動隔離・凍結に至らなかった)の eBPF 重大イベント、およびクリティカルパス FIM・依存関係ロックファイルの改ざん検知です。有効化すると、デーモンはローカルのヒューリスティックによる一次判定レポート(誤検知らしさ・根拠・次の調査ステップ)を生成した上で、そのレポートを添えて設定済みの AI エージェント CLI をサブプロセスとして起動し、追加調査と要約を委任します。既定は無効です。

# /etc/roamswitch/guard.yaml に追記(server.conf とは別ファイル)
investigation:
  enabled: true
  command: "claude"                 # "claude" | "agy" | "codex" | "opencode" | 任意のカスタムコマンド
  args: ["-p"]                      # プロンプトは自動的に末尾の引数として追加される
  timeout_secs: 180

sudo roamswitch server setup の対話式ウィザード(§3.2 最終ステップ)からも同じ内容を設定できます。ツールは Claude Code(claude -p)/ agy(agy -p --dangerously-skip-permissions)/ Codex CLI(codex exec)/ OpenCode(opencode run)のプリセット、または任意のカスタムコマンドから選択します。再実行時は保存済みの設定から該当するプリセットが自動選択されます。

プロンプトインジェクションへの注意
investigation 連携は、検知イベントの詳細(プロセス名・ファイルパス等、攻撃者が部分的に制御しうる文字列を含みうる)を、長さを切り詰めた JSON の文字列として、AIエージェントへのプロンプトに渡します。JSON の文字列にすることで、改行や引用符を使って、区切り線や偽の指示欄を作れないようにしています。ただし、文字列の中身が指示として読まれる可能性は、なくなりません。そのため、エージェントは root では実行できません。run_as に一般ユーザーを指定しない設定は、ウィザード(CLI と TUI)が保存せず、デーモンも起動しません。特に --dangerously-skip-permissions 等の権限確認を省略するモードでツールを起動する設定は、悪意ある入力によってエージェントが意図しない操作を行うリスクを高めます。ウィザードは agy 選択時にこの注意を表示します。信頼できないログ入力経路を持つ環境では、権限確認を省略しないツール設定を推奨します。

ネットワーク遮断中(Air-Gap 発動時など、isolate_host_all 相当の状態)は investigation 連携は呼び出されません。

11. CLI コマンド早見表

コマンド 必要権限 機能概要
sudo roamswitch status --server root 40項目のサーバーセキュリティ診断スコアとステータスを表示(--json で同じ内容を JSON で出力)
sudo roamswitch ports root 現在外部露出しているポートおよびバインドプロセスの一覧
sudo roamswitch fim verify root クリティカルパス重要ファイルの SHA-256 整合性検査
roamswitch notifications 一般 直近1週間の通知履歴(送信済みの全アラート)を表示(--json / --export ファイル にも対応)
sudo roamswitch server setup root 対話型の初期セットアップウィザードを実行
sudo roamswitch fim update root 現在のファイル状態を元に FIM ベースライン DB を再生成
sudo roamswitch server config set <key> <値> root 設定キーを 1 つ変更して保存(一覧は §18)
sudo roamswitch emergency-restore root eBPF・ファイアウォールの緊急隔離を全解除し、初期ベースラインへ復帰
roamswitch port-scan 一般 着信ポートスキャン検知ガード(§4.3)の検知履歴(送信元IP、自動遮断の有無・継続時間)を表示
roamswitch sensor [key|list|pair|repin|unpair|request-audit|results] 一般/root RoamSwitch Sensor とのペアリングコード方式による相互信頼。pair --addr <IP> --code <コード> --fingerprint <SHA-256>でペアリング(フィンガープリントはSensorの画面に表示される値)、repinでSensorの証明書再生成後にピンを更新、request-auditで能動的脆弱性監査をリクエスト(結果はresultsで取得)。sensor_pairing_enabled: true(既定 OFF)が必要。詳細は Sensor 運用マニュアル を参照
roamswitch server tui 一般/root 全機能を操作できる TUI を起動(一般ユーザーは読み取り専用、root で全操作。§17)
sudo roamswitch server ack root 現在の緊急隔離を確認応答(FULL 隔離中は厳格な隔離を維持し、縮退中は解除。§4.1)
roamswitch server ebpf 一般 eBPF ランタイムガードの現在の隔離状態と検知済みインシデント履歴を表示
roamswitch server bans [--json] 一般/root SSH 総当たりガード(§4.5)の状態と、いま禁止している接続元(残り時間つき)を表示。--json あり。禁止の一覧は root だけが読めます
sudo roamswitch server unban <IP | IPv6 /64 | --all> root SSH 総当たりガードの禁止を解除し、その接続元の失敗の回数も消します(IPv6 は /64 で指定、--all で全件)
sudo roamswitch server allowlist [list|add <rule> <process> [--exe <path>] [--days N] <reason...>|renew <rule> <process> [--days N]|remove <rule> <process>] root eBPF ガードの許可リスト(誤検知の許可)を管理。add は末尾に理由が必須で、--days N(1〜365、既定 30)の期限が付きます。--exe を付けると実行ファイルのパスに紐づき、プロセス名の偽装では回避できません(名前だけの登録は偽装可能で、警告が表示されます)。renew は、いまから数えて新しい期限(既定 30 日)を付け、期限切れの項目を戻し、古い項目に期限を付けます。期限が切れた項目は効かなくなります。list は期限、残り日数、追加した人、理由を表示します
roamswitch server allowlist test <rule> <process> [--exe <path>] 一般/root 何も変更せずに、そのルールとプロセスの組み合わせが許可リストにあるか、プロセスが凍結・kill から保護されているか(protected_processes を含む)を表示します。名前とパスの代わりに --pid PID でも指定できます。期限切れの項目が理由でガードが動くときは、そう表示します。
roamswitch frozen [list|resume <PID>|resume all] 一般/root ガードが SIGSTOP で凍結中のプロセスの一覧と、誤検知だった場合の解除(SIGCONT)。resume all で全件解除
sudo roamswitch events search|tree|export|verify|status root 実行レコーダーの検索(--since 2h --pid --exe --user --parent --text --json)、プロセスツリー、エクスポート、ハッシュチェーンの検証、状態表示(§15)
sudo roamswitch forward status|test|queue root イベント転送の状態確認・テストイベントの送信・再送キューの表示(§16)
roamswitch timeline / notifications [--json] [--export FILE] 一般 統合インシデントタイムライン(実験的)/通知履歴の表示。--json で JSON 配列、--export ファイル で JSON Lines を出力
roamswitch audit-agent-setup [--json | --format text|json|sarif] 一般 AI エージェントに読ませる指示ファイル・スキルと OpenClaw の設定の危険な記述を、読み取り専用で点検(ホームフォルダーのみ。通信しない。--json で JSON)。Server TUI の「ツール」からも実行できる。各指摘に OWASP LLM と ATT&CK のタグが付き、--format sarif で SARIF 2.1.0(CI・コードスキャン向け)を出せる。エージェントのメモリのフォルダーも読む。Claude Code の設定(~/.claude/settings.json)も読む。
roamswitch detection-coverage [--server | --client] [--json | --format json|navigator] 一般 攻撃者が防御を弱めたり、痕跡を消したり、居座ったりするときに使う MITRE ATT&CK の手法ごとに、RoamSwitch が検知できるか(検知 / 一部 / 見ていない)、見えない部分、担当するガードがいま動いているか(稼働中 / 停止中 / 一部のみ稼働 / 不明)を一覧にする(コードと手元の設定を読むだけで、何も実行せず、通信もしない)。手法ごとに根拠のコードが残っていることをテストで確かめていて、一覧にない手法は評価していない。Client Edition は 34 手法(一部 27・見ていない 7)、Server Edition は 34 手法(一部 24・見ていない 10)で、検知と評価した手法はない。--server / --client で Edition を選ぶ(省略時はインストール済みの Edition)。--json で MCP ツール get_detection_coverage と同じ JSON。Server TUI の「ツール」からも実行できる。 --format navigator で、同じ表を MITRE ATT&CK Navigator のレイヤー(layer 4.5)として出力する(検知は緑、一部は黄、見ていないは赤。評価していない手法は色を付けない)。
roamswitch vuln-accept <ECOSYSTEM> <PACKAGE> <CVE-ID> --version VERSION --reason TEXT [--days N] | --list | --revoke ... 一般 パッケージ CVE 照合の検出に対する、人による期限つきの「受容」を記録・一覧・取り消しする。roamswitch vuln-accept <エコシステム> <パッケージ> <CVE-ID> --version VERSION --reason 理由 [--days 日数](理由は必須。期限は既定 90 日、最長 365 日)、--list、--revoke。受容した検出は結果に残り、「要対応」の数から外れて別に数えられる。期限が切れると通常の検出に戻り、通知履歴に記録される。MCP からは受容できない。受容は、指定したインストール済みバージョンにだけ効く
roamswitch sigma-check [RULE.yml] [--against-exec-history] [--json] 一般/root 自分で /etc/roamswitch/sigma.d に置いた Sigma ルール(process_creation の一部の書き方)が読み込めるかを確かめる。--against-exec-history は、記録済みの実行(exec)イベントに対して試験的に一致を調べる(実行ログは root 専用)。読み込めなかったルールは理由つきで一覧に出る。ルールは root 所有で、ほかのユーザーが書き込めないファイルだけを読む。実行レコーダーでの一致は通知・タイムライン・転送だけで、遮断はしない。ネットワーク接続なし
roamswitch doctor [--json] 一般/root この端末が、RoamSwitch の防御を支えられる状態かを調べます(何も変更せず、ネットワークにも出ません)。防御が使うツール(nft・systemctl・journalctl・ip は必須、curl・dig・ss・clamscan は任意)があるか、それらと RoamSwitch 自身のファイルが root 所有で root 以外に書き込めないか、パッケージ管理(dpkg --verify / rpm -V)の検証でインストール時と同じか、root で実行しているか、/usr/local/share/ca-certificates などに手で入れた CA 証明書があるか、RoamSwitch のデーモンが動いているか、ufw・firewalld・fail2ban・crowdsec-firewall-bouncer が動いているか(動いていれば注意)を確かめます。--json で JSON を出します。失敗が 1 件でもあれば終了コード 1(注意だけなら 0)。Server TUI の「ツール」からも実行できます。
roamswitch scan-frontend <FOLDER> 一般/root Web サイトのフロントエンドが公開するファイルを読んで、埋め込まれた API キー・秘密鍵・暗号資産の鍵(audit-secrets と同じ検出)、JWT、本番に置いてはいけないソースマップ(.map のファイル)を探します。フォルダーの中の dist・build・out・.next/static・.output/public・.svelte-kit/output・public・static を読み、どれもなければフォルダーそのものを読みます。audit-secrets <フォルダー> は dist と build を調べないので、公開される側はこのコマンドで調べます。見つけた値は、先頭 4 文字と長さだけ表示します。何か見つかれば終了コード 1。上限は、1 ファイル 5 MB、全体 200 MB、20,000 ファイルです。シンボリックリンクはたどりません。Server TUI の「ツール」からも実行できます。

12. トラブルシューティング & FAQ

Q1. SSH 接続が突然切断される可能性はありますか?

ありません。RoamSwitch は通常時、既知の ESTABLISHED/RELATED 接続ステートおよび ssh_ports(既定 22)を無条件に許可します。また、重大攻撃による Air-Gap 緊急隔離の発動時でも preserve_ssh_on_isolation=true(既定)により、保守 SSH ポートと管理元 IP の管理セッションは温存されます(それ以外の確立済み接続は遮断されます)。1.10.4 以前のサーバー版には、隔離用ルールが SSH の応答を落とし、隔離中は管理者の SSH が通らない不具合がありました。1.10.5 で修正しています。

Q2. 誤設定で自分自身がアクセス不能になった場合は?

クラウド事業者の Web 管理コンソール(VNC / シリアルコンソール)からログインし、sudo roamswitch emergency-restore で緊急隔離を解除するか、sudo systemctl stop roamswitch-server を実行してサービスを停止してください。

Q3. Nginx や Apache、Node.js などの Web サーバーを後から追加した場合は?

sudo roamswitch server config set allowed_ports 80,443,3000(または /etc/roamswitch/server.conf を直接編集)し、sudo roamswitch server restart を実行してください。allowed_ports は TCP のポートです。UDP を受け入れるときは、allowed_udp_ports に書きます。

13. File Scan Guard (メール中継・共有フォルダ・アップロード先の保護)

メール中継、Samba/NFS 共有、ファイルアップロード先など、他者に渡すファイルを保管するディレクトリがある場合のみ関係する任意機能です。組み込み YARA エンジンは外部依存なしで clamav_scan_dirs を常時スキャンし、clamav_enabled=true にすると ClamAV によるセカンドオピニオンも追加されます。確認された脅威は自動的に /var/lib/roamswitch/quarantine へ隔離され、運用者に通知されます。

# --- File Scan Guard (ClamAV, オプトイン) ---
# 組み込みYARAは常時、clamav_scan_dirs を無条件でスキャンします。
clamav_enabled=false             # true で clamdscan による追加検査を有効化
clamav_scan_dirs=                # スキャン対象の絶対パス(カンマ区切り)
clamav_scan_interval_secs=86400  # スキャン間隔(秒、最小 3600)
clamav_freshclam_interval_secs=7200  # シグネチャDB自動更新間隔(秒、最小 1800)

対話式ウィザード sudo roamswitch server setup からも設定でき、get_file_scan_guard_status MCP ツールから設定・隔離状況を確認できます。

サービスが書き込めるのは、/var/www、/srv、/root、/opt、/etc/roamswitch の中などだけです。おとりの設置と、ファイルスキャンの隔離は、その外では失敗します。ほかの場所を使うときは、sudo systemctl edit roamswitch-server を開き、[Service] の下に ReadWritePaths=-/パス を足して、サービスを再起動します。roamswitch doctor が、書き込めない場所を警告します。

YARA や ClamAV で見つけたのに、隔離できなかったファイルは、ファイル名と理由つきで通知します。

14. Log Audit (ログパターンの異常検知・通知)

journalctl の定期スキャンにより、ログメッセージをテンプレート化して新規パターン(過去に見られなかったログ形式)と頻度異常(統計的なスパイク、Z-score > 3.0)を検知します。追加のデーモンや外部依存を必要としない軽量な仕組みのため、既定で有効です。検出時は File Scan Guard や FIM と同じ通知チャネル(Telegram / LINE / Webhook)へ通知されます。ログに含まれる可能性のあるAPIキーやトークンは、通知本文へ渡す前に自動的にマスキングされます。

# --- Log Audit (既定で有効) ---
log_audit_enabled=true           # false で無効化
log_audit_interval_secs=1800     # スキャン間隔(秒、最小 300)

対話式ウィザード sudo roamswitch server setup からも設定でき、roamswitch audit-logs CLI コマンドまたは audit_security_logs MCP ツールから最新の検知結果を確認できます。

アクセスログの診断ツール AccessScope(別ツール): Server Edition の deb / rpm には、Web サーバと SSH のアクセスログを診断する別ツールのコマンド accessscope が同梱されます(画面アプリは入りません)。読み取り専用で、解析には LLM もネットワークも使いません。LLM による分析は任意で、既定はオフです(有効にしたときは、root でも実行できますが、LLM が root の権限で動くので、権限のないユーザーでの実行をお勧めします)。accessscope scan --auto でこのホストのログを診断し、accessscope discover でログの場所を一覧にします。詳細は AccessScope のページをご覧ください。

15. 実行レコーダー & 相関ルール(ローカル、通知専用)

プロセスの実行(exec)を、netlink の proc connector で記録します(実行ファイル・引数・親プロセスの系譜・UID・cwd・コンテナID・SHA-256)。既定でオンで、記録はこのホストの root 専用ディレクトリ /var/lib/roamswitch/exec_log に、ハッシュチェーン付きのセグメント化 JSON Lines(0600、既定 200 MB / 14 日)として保存され、外部へは送信されません。改ざんを検知できる(events verify)もので、改ざんを防止するものではありません。proc connector を開けない場合は、低頻度の /proc 走査に縮退します。記録処理は fanotify の許可イベントループとは別スレッドで動き、自身の CPU 使用率を予算(既定 3%)内に抑えます。 書き込み中のセグメントは、記録が入っているあいだ 10 秒ごとに、そこまでの全バイトのハッシュを持つ checkpoint の行で守られ、events verify が、チェックポイントより前の記録の編集・削除・入れ替えを検知します。最新のチェックポイントより後の記録(最大でおよそ 10 秒分)は検証できず、verify がその件数を表示します。ファイルごとの削除は「検証対象なし」と返ります。

ローカルで検索・エクスポートします(root 専用ログのため通常 sudo が必要です)。

sudo roamswitch events status
sudo roamswitch events search --since 2h --exe curl
sudo roamswitch events tree 4242
sudo roamswitch events export --since 1d > exec-events.jsonl
sudo roamswitch events verify

相関ルール EXR-001〜012(通知専用・MITRE ATT&CK 技術ID付き): EXR-001 Web サービスから起動されたシェル / EXR-002 /tmp・削除済み・memfd からの実行 / EXR-003 ダウンロード直後の実行 / EXR-004 base64・eval を使うインタプリタのワンライナー / EXR-005 setuid 由来でない uid 0 の取得 / EXR-006 cron・systemd が直前に配置したバイナリの実行 / EXR-007 ld.so.preload 変更後の実行。各ルールは静かな既定値で、単独では隔離しません。exec_rules_disabled(例 EXR-003,EXR-004)で個別に無効化し、exec_rule_service_users で EXR-001 の対象ユーザーを調整できます。

EXR-008 から EXR-012 は次のとおりです。EXR-008 既知の管理ツール以外によるスナップショット(btrfs、LVM)の削除 / EXR-009 同じ実行元が 30 秒以内に 8 種類以上の攻撃ツールを探したこと / EXR-010 スクリプト処理系が 2 分以内に 3 種類以上の攻撃ツールを起動したこと / EXR-011 起動時のコマンドラインにあるリバースシェルの形(重大度 高) / EXR-012 サービスのアカウント、またはサービスの配下の root 以外のプロセスが、gdb・strace・ltrace をプロセス ID 付きで起動したこと(重大度 中)。EXR-004 は、$SHELL で指したシェルや python の os.system も対象にします。EXR-003 は、curl -fsSLo や wget -qO のようにまとめたオプションも読みます。EXR-011 と EXR-012 は、コマンドラインの文面で判定し、貼り付けた例文やテストも同じ形に見えるため、exec_rules_isolation_optin を true にしても隔離のトリガーになりません。

EXR-013 は、サービス(nginx・apache・php-fpm など)から起動された、またはサービスのアカウントのプロセスが、システムのディレクトリ(/usr/lib・/lib・/usr/lib64・/lib64・/usr/libexec・/usr/local/lib)の外のライブラリを指す LD_PRELOAD か LD_AUDIT、または書き込める場所(/tmp・/var/tmp・/dev/shm・/home・/var/www)を指す LD_LIBRARY_PATH を付けて動いたときに通知します。前者は重大度が高、後者だけなら中です。通知だけで、隔離のきっかけにはなりません。PHP の disable_functions を putenv("LD_PRELOAD=…") と mail() で回避する手口に対応します。EXR-007 は /etc/ld.so.preload のファイルの変更しか見ません。root と一般のログインユーザーは対象外です。記録するのは、この 3 つの変数の値だけです(最大 200 文字、パスだけ。ほかの環境変数は読まず、保存もしません)。EXR-009 と EXR-010 の攻撃ツールの名前に、chisel・ligolo・ligolo-ng・suo5・stowaway・iox・venom・mimikatz・certipy・rubeus・kerbrute・bloodhound・sharphound・sliver・havoc・godzilla・behinder・antsword・weevely・fscan・gogo・ehole・oneforall・ksubdomain・dnsx・subzy・tlsx・cdncheck と impacket 系を加えました。frp・nps・ngrok・ssh は、日常に使うので入れていません。EXR-010 は、実行ファイルが /home・/root・/tmp などにあるプログラム(ツールを自分で動かすエージェント)にも広げました。端末・ssh・login の連鎖は、これまでどおり対象外です。

exec_rules_isolation_optin(既定 false)を明示的に true にした場合に限り、重大度の高いルールが高信頼度の隔離トリガーとして扱われます(§4.1)。既定では、ルールは通知するだけです。読み取り専用の MCP ツール search_exec_events / get_process_tree からも参照できます。

16. イベント転送(SIEM / syslog / Webhook、オプトイン)

検知イベントを、運用者が指定した自組織の宛先へ転送できます。既定はオフで、forward_enabled=true にして宛先を1つ以上設定するまで何も送信されず、ベンダーの宛先は組み込まれていません。転送対象はインシデントタイムライン・通知履歴・eBPF ガード・隔離の状態遷移・FIM・カナリア・exec ルールのアラートで、exec の生ログ自体は転送しません。宛先は次の3種類です。

送れなかったイベント(TCP / TLS / Webhook)はディスクの再送キュー(0600、forward_queue_max_mb 既定20 と forward_queue_max_age_hours 既定72 で上限)に積まれ、再起動後も指数バックオフで再送されます。forward_min_severity(既定 low)で送る重大度の下限を設定できます。ホットパスを止めないよう、送信は専用スレッドで行い、キューが溢れた場合は最古のものから破棄します。

状態確認・疎通テスト・再送キューの確認は次のコマンドで行います(設定を変えたらサービスを再起動してください)。

# /etc/roamswitch/server.conf
forward_enabled=true
forward_syslog_url=tls://siem.example.org:6514
forward_syslog_format=rfc5424
forward_tls_ca_file=/etc/ssl/certs/corp-ca.pem
forward_min_severity=medium

sudo roamswitch server restart
sudo roamswitch forward status
sudo roamswitch forward test
sudo roamswitch forward queue

17. Server TUI(全機能を操作できるターミナルUI)

roamswitch-server-tui(roamswitch server tui でも起動)は、CLI の動詞と 2 つの設定ファイル(server.conf と guard.yaml)に散らばる Server Edition のすべての機能を、SSH 越しのキーボード操作だけで扱える TUI です(80x24 で動作、10言語)。CLI の動詞または設定キーが増えたのに TUI に対応物がない状態は、開発時のテストで失敗するようになっています。

起動:

sudo roamswitch-server-tui    # full control
roamswitch-server-tui         # read-only
roamswitch server tui         # same as roamswitch-server-tui

18. 設定キー一覧(server.conf / guard.yaml)

roamswitch-server は設定の再読み込み(reload)に対応していません。すべてのキーの反映にはサービスの再起動が必要です(sudo roamswitch server restart)。server.conf の各キーは roamswitch server config set <キー> <値> または §17 のエディタで変更できます。guard.yaml はエディタか手動編集で変更します。

18.1 server.conf(122 キー)

キー 型 / 取り得る値 既定 再起動
languagesystem | ja | en | zh-hans | zh-hant | ko | de | fr | es | it | ptja要
ssh_portsport list22要
allowed_portsport list80,443要
allowed_udp_portsport list—要
whitelist_ipslist—要
preserve_ssh_on_isolationbooltrue要
telegram_enabledboolfalse要
telegram_bot_tokenstring—要
telegram_chat_idstring—要
line_enabledboolfalse要
line_channel_access_tokenstring—要
line_user_idstring—要
webhook_enabledboolfalse要
webhook_urlURL—要
webhook_block_private_addressesboolfalse要
webhook_allow_addressesCIDR list—要
action_on_criticalisolate | freeze | alert_onlyisolate要
ebpf_socket_pathabs path/run/roamswitch/events.sock要
ebpf_heartbeat_interval_secs0 (off) | int 60–86400600要
isolation_hard_cap_secsint ≥ 03600要
protected_processeslist—要
fim_check_interval_secsint ≥ 10300要
fim_intrusion_context_enabledbooltrue要
protect_docker_portsbooltrue要
dns_sinkhole_enabledboolfalse要
dns_sinkhole_providerquad9 | cloudflare | adguard | cleanbrowsingquad9要
egress_ip_blocklist_enabledbooltrue要
metadata_watch_enabledbooltrue要
metadata_watch_allow_userslist—要
cve_kernel_map_updates_enabledbooltrue要
clamav_enabledboolfalse要
clamav_scan_dirsabs path list—要
clamav_scan_interval_secsint ≥ 360086400要
clamav_freshclam_interval_secsint ≥ 18007200要
lockfile_watch_dirsabs path list—要
domain_check_domainsdomain list (max 5)—要
npm_audit_signatures_enabledboolfalse要
log_audit_enabledbooltrue要
log_audit_interval_secsint ≥ 3001800要
resource_guard_enabledbooltrue要
resource_guard_rss_sample_interval_secsint ≥ 1060要
resource_guard_rss_window_secsint ≥ 3001800要
resource_guard_rss_min_samplesint ≥ 310要
resource_guard_rss_min_growth_pctint ≥ 1050要
resource_guard_rss_recovery_drop_pctint ≥ 110要
resource_guard_rss_min_kbint ≥ 020000要
resource_guard_crash_loop_window_secsint ≥ 60300要
resource_guard_crash_loop_min_restartsint ≥ 23要
resource_guard_correlation_window_secsint ≥ 60600要
resource_guard_zombie_min_countint ≥ 020要
resource_guard_load_min_normalizedfloat ≥ 0.00.5要
resource_guard_load_enabledbooltrue要
resource_guard_cpu_enabledbooltrue要
resource_guard_cpu_sample_interval_secsint ≥ 060要
resource_guard_cpu_window_secsint ≥ 0600要
resource_guard_cpu_min_samplesint ≥ 08要
resource_guard_cpu_pct_thresholdfloat 0.0–1000.080.0要
container_exec_guard_enabledbooltrue要
port_scan_detect_enabledbooltrue要
port_scan_detect_min_distinct_portsint ≥ 015要
port_scan_detect_window_secsint ≥ 0300要
port_scan_block_enabledbooltrue要
port_scan_block_duration_secsint ≥ 0600要
ssh_guard_enabledbooltrue要
ssh_guard_block_enabledbooltrue要
ssh_guard_max_failuresint 2–10006要
ssh_guard_window_secsint 10–86400600要
ssh_guard_ban_secsint 60–25920003600要
ssh_guard_ban_max_secsint 60–31536000604800要
ssh_guard_allowIP/CIDR list—要
ssh_guard_log_sourceauto | journal | abs pathauto要
ssh_guard_yield_to_fail2banbooltrue要
ssh_guard_trust_key_daysint 0–365030要
agent_usersname list—要
agent_egress_allowaddr[:port] list—要
agent_setup_watch_secsint 0, 30–86400300要
prn_recordboolfalse要
prn_retention_hoursint 1–16824要
prn_max_mibint 16–4096256要
dns_tunnel_detect_enabledbooltrue要
reverse_shell_watch_enabledbooltrue要
reverse_shell_watch_include_privateboolfalse要
reverse_shell_watch_notify_onlyboolfalse要
suid_watch_enabledbooltrue要
suid_watch_interval_secs60 – 86400600要
log_tamper_watch_enabledbooltrue要
sensor_pairing_enabledboolfalse要
sensor_colocatedboolfalse要
forward_enabledboolfalse要
forward_syslog_urlURL—要
forward_syslog_formatrfc5424 | cefrfc5424要
forward_syslog_framingoctet | lfoctet要
forward_tls_ca_fileabs path—要
forward_file_pathabs path—要
forward_file_max_mbint 1–1000050要
forward_file_keepint 1–1005要
forward_file_groupグループ名 | gid—要
forward_webhook_urlURL—要
forward_webhook_secretstring—要
forward_webhook_secret_fileabs path—要
forward_webhook_block_private_addressesboolfalse要
forward_webhook_allow_addressesCIDR list—要
forward_min_severityinfo | low | medium | high | criticallow要
forward_queue_max_mbint 1–1000020要
forward_queue_max_age_hoursint 1–876072要
exec_recorder_enabledbooltrue要
exec_recorder_retention_mbint 1–100000200要
exec_recorder_retention_daysint 1–365014要
exec_recorder_hash_chainbooltrue要
exec_recorder_hash_exebooltrue要
exec_recorder_cpu_budget_pctint 1–503要
exec_rules_enabledbooltrue要
exec_rules_disabledstring—要
exec_rule_service_usersstring—要
exec_rule_window_secsint 5–3600120要
exec_rules_isolation_optinboolfalse要
honeytokens_enabledbooltrue要
honeytokens_homeabs path/root要
harden_userns_enabledbooltrue要
dns_tunnel_entropy_allowlistlist—要
fleet_forwarding_allowed_hostslist—要
fleet_forwarding_allow_plaintextboolfalse要

18.2 guard.yaml(37 キー)

キー 型 / 取り得る値 既定 再起動
sensor.typefalco | tetragonfalco要
sensor.transportstringunix_socket要
sensor.socket_pathabs path/run/roamswitch/events.sock要
sensor.reconnect_interval_msint 100–36000001000要
policies.on_warning.actionlog_and_notify | isolate_process | isolate_host_alllog_and_notify要
policies.on_warning.server_actionlog_and_notify | isolate_process | isolate_host_all—要
policies.on_warning.kill_processboolfalse要
policies.on_warning.freeze_processboolfalse要
policies.on_warning.fallback_to_host_isolation_after_retriesint 0–1000—要
policies.on_error.actionlog_and_notify | isolate_process | isolate_host_allisolate_process要
policies.on_error.server_actionlog_and_notify | isolate_process | isolate_host_all—要
policies.on_error.kill_processboolfalse要
policies.on_error.freeze_processboolfalse要
policies.on_error.fallback_to_host_isolation_after_retriesint 0–1000—要
policies.on_critical.actionlog_and_notify | isolate_process | isolate_host_allisolate_process要
policies.on_critical.server_actionlog_and_notify | isolate_process | isolate_host_all—要
policies.on_critical.kill_processbooltrue要
policies.on_critical.freeze_processboolfalse要
policies.on_critical.fallback_to_host_isolation_after_retriesint 0–10003要
policies.on_emergency.actionlog_and_notify | isolate_process | isolate_host_allisolate_host_all要
policies.on_emergency.server_actionlog_and_notify | isolate_process | isolate_host_all—要
policies.on_emergency.kill_processbooltrue要
policies.on_emergency.freeze_processboolfalse要
policies.on_emergency.fallback_to_host_isolation_after_retriesint 0–1000—要
server_safeguards.allow_established_connectionsbooltrue要
server_safeguards.maintenance_ssh_portsport list22要
server_safeguards.whitelist_cidrslist—要
server_safeguards.whitelist_dns_serverslist—要
server_safeguards.safety_timer_secsint ≥ 0300要
adaptive_security.enabledbooltrue要
adaptive_security.untrusted_network_actions.elevate_falco_profilestring—要
adaptive_security.untrusted_network_actions.auto_enable_userns_restrictionboolfalse要
investigation.enabledboolfalse要
investigation.commandabs path—要
investigation.argslist—要
investigation.run_asstring—要
investigation.timeout_secsint 1–86400180要

19. Webシェル・注入コードの検知 (Web Rules)

Web サーバーで動く PHP・JavaScript・JSP・ASP の Web シェルや注入コード、画像に偽装した PHP、.htaccess の悪用を見つける機能です(Linux 1.11.23 以降)。設定は要りません。Apache・nginx・Caddy の設定からドキュメントルートを自動で見つけ、そこにファイルが書き込まれて閉じられた直後に検査します(通知専用の fanotify を使うため、ほかのプロセスを止めることはありません)。取りこぼしと、起動前からあるファイルのために、6 時間ごとの定期スキャンも行います。ClamAV は不要です。

動作は /etc/roamswitch/web_rules.conf(root 所有、権限 0644。他人が書き込めるファイルは無視されます)で決めます。変えたら sudo roamswitch server restart で反映します。雛形は roamswitch web-rules example conf で出せます。

キー意味(既定値)
quarantine自動で隔離する水準。off は隔離しない、critical は重大だけ、high は重大と高(既定: critical)。高を critical 水準で見つけたときは、通知とフックだけで、ファイルには触れません。
account_action検知したファイルの持ち主のログインシェルを無効にするか。off / revoke_shell(既定: off)。隔離できた重大・高だけが対象です。root、account_min_uid 未満、ログイン中のユーザー、デーモン自身には何もしません。元のシェルは記録され、sudo roamswitch web-rules unlock-account <ユーザー> で戻せます。
account_min_uidaccount_min_uid: アカウント対処の対象にする最小の UID(既定 1000、100 未満には下げられません)。
scan_dirs, auto_docroots, watch, scan_interval_secsscan_dirs(カンマ区切りの絶対パス)、auto_docroots(既定 true。false で、Web サーバー設定からの自動検出をやめる)、watch(既定 true。false で、書き込み監視をやめて定期スキャンだけにする)、scan_interval_secs(既定 21600、最小 3600)。
# /etc/roamswitch/web_rules.conf
quarantine=critical
account_action=off
account_min_uid=1000
#scan_dirs=/srv/sites/a,/srv/sites/b
auto_docroots=true
watch=true
scan_interval_secs=21600

検知すると、通知(Telegram・LINE・Webhook)が届きます。通知には、ルール名と、隔離したか、そのまま残したかが書かれます。確認だけで動かしたいときは、sudo roamswitch web-scan <フォルダー>(確認のみ。--quarantine で隔離)、または Server TUI の「ツール」画面を使います。独自ルールは /etc/roamswitch/rules.d/*.json、検知後に自分のスクリプトを呼ぶ場合は /etc/roamswitch/hooks/post-detection(検知の内容が JSON で標準入力に渡されます。root 所有で、他人が書き込めず、親ディレクトリも安全なものだけが実行されます。シェルは通らず、30 秒で打ち切られます)です。ルールの更新は、日次の受信専用アップデートに含まれます。cve_kernel_map_updates_enabled を無効にすると、通信は一切行われず、同梱のルールで動作します。

roamswitch web-rules status は、いま監視しているドキュメントルートの件数と一覧を表示します。0件のときは、Web シェルの検知は何も見ていません。web_rules.conf の scan_dirs に、フォルダーを書いてください。リバースプロキシの裏のアプリや、標準外の nginx の構成では、自動で見つからないことがあります。0件のときは、起動時にも警告を1回出します。

20. AI エージェントを動かすアカウント(agent_users)

AI エージェント(Claude Code、Codex CLI など)を専用のアカウントで動かすときの機能です(Linux 1.12.7 以降)。NIST・NSA・CISA が 2026 年 10 月 16 日に出した、AI エージェントの事故への備えと対応の手引き(Key Practices for Incident Readiness and Response、10 の取り組み)のうち、1 台のサーバーで実装できる項目に当たります。専用のアカウントで動かす利点は、エージェントがそのアカウントでは、root のファイルとファイアウォールの規則を書き換えられないことです。

server.conf の 3 つのキーで設定します。反映には再起動が必要です(sudo roamswitch server restart)。

キー意味(既定値)
agent_usersエージェントを動かすアカウント(名前または数値の uid、カンマ区切り。既定は空)。root は拒否します。存在しない名前とホームのないアカウントは、起動時に警告して無視します。
agent_egress_allowagent_users が接続してよい宛先(既定は空 = 制限なし)。ADDR、ADDR/PREFIX、ADDR:PORT、ADDR/PREFIX:PORT、IPv6 は [ADDR]:PORT。名前は書けません。1 件でもあると、それ以外への新しい外向きパケットを落とします。0.0.0.0/0 のようにポートのない全許可は拒否されます(0.0.0.0/0:443 は可)。
agent_setup_watch_secs各アカウントの指示ファイル・スキル・MCP 定義を、root が持つ基準線と照合する間隔(秒。既定 300、0 で無効、30〜86400)。
# /etc/roamswitch/server.conf
agent_users=agent
agent_egress_allow=10.0.0.5:3128
agent_setup_watch_secs=300

外向きの許可リスト: nftables の inet roamswitch_agent_egress(output フック、優先度 -145)が、対象アカウントのソケットについて、ループバック、確立済みの通信、リストの宛先(ポートつきは TCP と UDP の両方)を通し、残りを rs_agent_egress_drop: のログ(毎分 10 件まで)を残して落とします。ほかのテーブル(悪性 IP の遮断など)は通常どおり評価されます。落とされた通信は 60 秒ごとにカーネルログから拾い、アカウントと宛先ごとに 1 時間に 1 回通知します。想定している使い方は、プロキシを 1 台だけ許可する形です(agent_egress_allow=10.0.0.5:3128)。リストはアドレスで照合するので名前では絞れず、AI の提供元のアドレスは変わることがあるため、名前の代わりにネットワークの範囲かプロキシで受けます。

root が持つ基準線: 指示ファイル(CLAUDE.md、AGENTS.md など)、スキル、MCP サーバー定義の確認済みの状態を、SHA-256 だけで /var/lib/roamswitch/agent_setup/<アカウント>.json(root 所有、0600)に置きます。そのアカウントでは書き換えも削除もできません。作成と更新は root で次のコマンドを実行します。内容に指摘が残るときは --accept-with-findings を使います。

sudo roamswitch audit-agent-setup --user agent             # check only
sudo roamswitch audit-agent-setup --user agent --accept    # make this state the baseline

デーモンは、設定ファイルの変更を inotify で約1秒以内に見つけます。agent_setup_watch_secs ごとの照合は、取りこぼしを拾う予備です。そのアカウントのホームと、ホーム直下の作業コピー(.git、.mcp.json、CLAUDE.md、AGENTS.md のどれかがあるフォルダー、最大 50)を監査して基準線と比べ、変更・追加があれば、同じ変更の組を繰り返さずに 1 回通知し、インシデントタイムラインに記録します。基準線がまだ無いアカウントは、比べられません。起動時と5分おきに調べ、基準線のないアカウントがあれば、そのコマンドを添えて通知します(同じアカウントにつき24時間に1回まで)。

おとりの認証情報: §7.4 のおとりを、agent_users のホームにも置き、アカウントごとに監視を 1 つ起動します。/root のおとりは、別のアカウントで動くエージェントには開けません。

できないこと: 許可リストは対象アカウントのソケットだけを見るので、root になれるエージェントや、root 所有のサービスに代わりに接続させる経路は止められません。UDP は応答を待たないので、落とされても送った側は失敗に気づきません。Mac 版と Linux クライアント版には、この機能はありません。

人がログインして使うアカウントは入れないでください。.ssh/id_rsa がないアカウントには、偽の鍵が置かれます。ssh は既定の鍵として試すので、誤警報になることがあります。

サービスは /home を読み取り専用で見ます。そのため、ReadWritePaths=-/home/<アカウント> を足すドロップインがないと、おとりは置かれません。sudo roamswitch server setup の手順13が、確認のうえで書きます(/etc/systemd/system/roamswitch-server.service.d/agent-users.conf)。

設定の手順は、次のとおりです。(1) agent_users を設定します。(2) sudo roamswitch server restart を実行します。(3) sudo roamswitch audit-agent-setup --user NAME で点検します。(4) 内容を読んだうえで、--accept を付けて、基準線を作ります。(5) roamswitch detection-coverage で確認します。基準線を作るまで、そのアカウントの変更は見張られません。作ったあとの変更は、約1秒以内に通知します。

点検する場所は、~/.claude、~/.codex、~/.cursor、~/.gemini(antigravity-cli を含む)、~/.agents、~/.openclaw と、ホーム直下の作業コピーだけです。ほかは、点検も変更検知もしません。何もない状態も、基準線にできます。

21. Web アクセスガードと Web ゲート(識別子の総当たりの検知と遮断)

受付番号・注文番号・利用者 ID のような数字を、順に差し替えて問い合わせる動きを、Web サーバーのアクセスログから見つけます。AI を使った攻撃ツールは、多数の IP に仕事を分けるので、IP ごとの回数や User-Agent では見えません。ここでは、人が使うときには起きない、番号の問い合わせ方を見ます。nginx と Apache のアクセスログを自動で見つけ、書かれるそばから読みます。

次の 4 つを見ます。(1) 予約した番号(canary。発行しない番号)への問い合わせ。(2) 1 つの接続元で、存在しない番号への 404 が 5 回。(3) 1 つの接続元が、同じ場所に 20 種類以上の番号。(4) 複数の接続元が、番号を到着順にたどる動き(100 種類で認識します。そのあとは、続きの番号を聞く新しい接続元を、最初の 1 件で見つけます)。1 つのアドレスにブラウザが 3 種類以上見える場合(CDN・社内プロキシ・携帯会社の共有アドレス)は、遮断しません。

動作は /etc/roamswitch/web_guard.conf(root 所有)で決めます。既定は mode=warn(通知と記録だけで、何も遮断しない)です。変更は 30 秒以内に反映されます。

キー意味(既定値)
modemode: off / warn / block(既定: warn)。block は、該当した接続元を、80/443 への新しい接続から ban_secs 秒のあいだ遮断します。許可リスト(whitelist_ips)、ゲートウェイ、DNS、このホスト自身のアドレス、ブラウザが 3 種類以上見える共有アドレスは、遮断しません。私的アドレスも既定では遮断しません(ban_private=yes で遮断)。
ban_secs, window_secs, not_found_limit, distinct_limit, ban_privateban_secs(既定 3600)、window_secs(既定 600)、not_found_limit(既定 5)、distinct_limit(既定 20)、ban_private(既定 no)。
canarycanary: 発行しない番号の範囲を、場所つきで書きます(何行でも)。例: canary=/loan/status?receiptNo=2026100090-2026100099、canary=/api/orders/{n}=5000001-5000009。当たった接続元は、1 回目で検知します。
gate_*gate_budget_floor(既定 30)、gate_budget_factor(既定 3)、gate_learn_secs(既定 3600)、gate_socket(既定 /run/roamswitch/webgate.sock): Web ゲートの設定です(下を参照)。
# /etc/roamswitch/web_guard.conf
mode=warn
canary=/loan/status?receiptNo=2026100090-2026100099
#ban_secs=3600
#ban_private=no

検知すると、通知(Telegram・LINE・Webhook)が届きます。1 つの場所(パスと番号のパラメータ)につき、1 回の確認で 1 通です。通知には、理由と、遮断したか、通知だけかが書かれます。遮断(mode=block)は roamswitch emergency-restore で解除できます。確認だけで動かしたいときは、mode=warn のままにします。

Web ゲート(roamswitch-webgate)は、nginx の手前で、番号を受け取る場所だけを守る、任意の部品です。IP を替え続ける攻撃は、IP ごとの規則では止められないので、場所ごとに「新しい番号の問い合わせの 1 分あたりの予算」を持ちます(起動後の 1 時間は通常の量を学び、そのあとは、通常のいちばん多い 1 分の 3 倍、最低 30)。予算を超えると、まだ聞かれていない番号の問い合わせに 429 を返します(同じ番号の再読み込みは通します)。上の 4 つの判定も、問い合わせの時点で行い、最初の 1 件から 403 を返します。Unix ソケットだけで動き、ネットワークには出ません。止まっているときは、リクエストを通します(サイトを止めません)。使うには、sudo systemctl enable --now roamswitch-webgate で起動し、roamswitch web-gate-config /loan/status が出す nginx の設定を、サイトの設定に足します(何も変更しません)。mode=warn の間は、判定して記録するだけです。

sudo systemctl enable --now roamswitch-webgate
roamswitch web-gate-config /loan/status --proxy http://127.0.0.1:8080
sudo nginx -t && sudo nginx -s reload

ゲートが止まっても、nginx はリクエストを通すので、止まったことに気づけません。動いているかは、次のコマンドで確かめます。ゲートは10秒ごとに /run/roamswitch/webgate.status を更新し、その ts が60秒以上古いと、サーバーのデーモンが通知します。再起動後に enable を忘れたときも、is-enabled で分かります。

systemctl is-active roamswitch-webgate
systemctl is-enabled roamswitch-webgate
sudo cat /run/roamswitch/webgate.status

手元の試験(模擬の総当たり、毎秒 5 件)の結果です。番号を順にたどる 700 件は、9 個の IP に分けても、100 件を通したところで全部の IP が遮断され、遮断のあとに新しい IP が続きを聞くと、最初の 1 件で遮断されました。ゲートがあると、30 件で止まりました。IP を毎回替えてランダムな順番で聞く攻撃は、ゲートの予算で、1 分あたり 30 件に抑えられました(ゲートがなければ止まりません)。止められないのは、通常の量の範囲で、ゆっくり時間をかける攻撃です。根本の対策は、アプリケーションで、利用者ごとの認可を確認することです。自分のサイトが、ログインなしに番号ごとの別の内容を返していないかは、roamswitch web-id-check 'http://127.0.0.1/loan/status?receiptNo={n}' --start 202610060001 で確かめられます(連続した最大 10 件を GET で問い合わせるだけです)。

nginx・Apache のワーカーが、5 分のあいだに 3 回、クラッシュのシグナル(SIGSEGV・SIGABRT など)で終了したときも、通知します(エラーログを読みます)。ワーカーは親プロセスが作り直すので、サービスの再起動としては数えられません。脆弱性を突く攻撃の試行や、プログラムの不具合の目印になります。通知は同じサーバーにつき 1 時間に 1 回で、止めたり遮断したりはしません。

サイトの構造を調べる動きも、同じアクセスログから見つけます(識別子の総当たりとは別の検知です)。1 つの接続元について、window_secs(既定 600 秒)のあいだに、次の 4 つの理由で数えます。(1) path_walk は、存在しない(404)パスを 12 種類以上です。識別子を含むパスは数えません。(2) asset_harvest は、.js・.mjs・.css・.map を Referer なしで 40 件以上です。ブラウザは必ず Referer を付けます。(3) api_probe は、/api/ の異なるパスで 401・403・404 が 20 種類以上です。(4) param_fuzz は、同じパスに異なるクエリが 30 種類以上で、かつ半分を超える応答が 4xx のときです。

この検知の設定も、同じ /etc/roamswitch/web_guard.conf に書きます。recon=off|warn|block(既定 warn)、上の 4 つの上限 path_walk_limit・asset_harvest_limit・api_probe_limit・param_fuzz_limit(3〜10000)、除外の exempt_path=/health(先頭一致。何行でも)と exempt_ua=uptimerobot(部分一致、小文字、3 文字以上。何行でも)です。既定は通知だけです。接続元を遮断するのは、mode=block かつ recon=block のときだけです(ブラウザが 3 種類以上見える共有アドレスは、これまでどおり遮断しません)。通知の文面は識別子の総当たりとは別で、「Web サーバーに、サイトの構造を調べる動きがあります」と出ます。検索エンジンや監視サービスの巡回でも同じ形になるので、そのときは exempt_ua か exempt_path で除外します。Web ゲートは、ログの Referer と応答コードを見られないので、この 4 つは見ません(ログのガードだけです)。

手元の試験の結果です。Docker の nginx が書いた本物のアクセスログを、実際のパーサーとガードに通しました。python-requests の User-Agent で巡回する 1 つの接続元は、12 件目の 404 で path_walk、40 件目の JavaScript(全体の 70 件目)で asset_harvest、20 件目の API(110 件目)で api_probe、30 件目のクエリ(145 件目)で param_fuzz として見つかりました。ブラウザの通常利用(ページ 20、Referer 付きの JavaScript 60、検索ボックス 40 回。合計 120 件)では、1 件も出ませんでした。測れたのは、この数値の範囲だけです。

遮断してよい環境かは、roamswitch web-guard-check [--hours N] で点検できます(root。何も変更しません)。アクセスログと Web サーバーの設定を読み、リクエストの多い接続元、プライベートのアドレス(プロキシやコンテナのネットワーク)から来た割合、複数のブラウザが見える共有のアドレスから来た割合、訪問者のアドレスを復元する設定(Apache の RemoteIPHeader、nginx の real_ip_header と set_real_ip_from)の有無を調べて、「向いている」「一部にだけ効く」「向いていない」「判断できない(リクエストが 200 件未満)」を出します。向いていないときは終了コード 1 です。CDN や社内プロキシが前にあって、訪問者のアドレスが Web サーバーに渡っていないと、全員が 1 つのアドレスに見え、遮断が多くの訪問者を止めます。ブラウザの種類が 3 つ以上見えるアドレスは共有として遮断しませんが、応答の成功(2xx・304)が 5 件に 1 件に満たないアドレスは、User-Agent を替えていても共有とは見ません。CDN の名前は見分けません(通信せず、古くなる一覧も持たないため)。

通知のみの警告には、この点検の結果と、いまの設定が食い違うときだけ案内を付けます。遮断していないのに「向いている」ときは遮断の設定の書き方を、遮断しているのに「向いていない」ときは遮断をやめる方法を書きます(点検はログから求め、6 時間は使い回します)。それ以外は、通知のみであることだけを書きます。遮断したアドレスは、roamswitch unban <IP>(--all ですべて)で外せます。これは SSH ガードと Web アクセスガードの両方の遮断を外します。

22. 乱数の記録(prn_record。期限つき)

ランサムウェアは、ファイルの鍵を、被害に遭ったマシンの中で、OS の乱数から作ります。prn_record=true にすると、getrandom が返した乱数と、/dev/urandom・/dev/random の読み取りで返った乱数(8 バイト以上、先頭 128 バイトまで)を、期限つきで記録します。攻撃のあとに、そのファミリー向けの復号ツール(KDDI 総合研究所の Memento など)が、鍵を作り直すための材料です。RoamSwitch は復号しません。既定はオフです。Mac 版と Linux クライアント版にはありません。

キー意味(既定値)
prn_recordprn_record: 乱数を記録します(既定 false)。オフにすると、記録済みのものを、デーモンの起動時に消します。
prn_retention_hoursprn_retention_hours: 記録を残す時間(1〜168 時間、既定 24)。
prn_max_mibprn_max_mib: 記録が使うディスクの上限(MiB、16〜4096、既定 256)。
# /etc/roamswitch/server.conf
prn_record=true
prn_retention_hours=24
prn_max_mib=256

記録は、期限を超えて残しません。prn_retention_hours を過ぎた記録は、10 分ごとのファイルごと、ゼロで上書きして消します(期限から最大 10 分遅れます)。prn_max_mib を超えたら、古いものから消すので、混んだホストでは、残る時間が短くなります(長くなることはありません)。範囲外の値は、丸めずに拒否します。ファイルの名前が示す時刻と更新時刻のどちらかが期限を過ぎていれば消すので、時計が狂っていても、期限は延びません。 デーモンが止まっているあいだは、期限の過ぎた記録を消す者がいないので、パッケージが入れる systemd-tmpfiles の設定(/usr/lib/tmpfiles.d/roamswitch-prn.conf)が、7 日(許す最大の保持時間)のあいだ書き込まれていないファイルを、1 日 1 回の掃除で消します(systemd 247 以降)。

方式は、カーネルの乱数の関数(get_random_bytes_user、urandom_read_iter、random_read_iter)への fentry/fexit です。乱数が作られるときだけ動くので、ほかのファイルの読み取りにも、ほかのシステムコールにも、負荷はかかりません。実測では、ファイルの読み取り 100 万回の差は誤差の範囲で、乱数の記録は 1 件あたり約 1.2 マイクロ秒、約 67 バイトでした(Linux 7.0、aarch64)。fentry が使えないカーネル(x86-64 は 5.5 未満、arm64 は 6.0 未満)や、これらの関数がないカーネルでは、記録を始められず、理由つきで通知します。負荷が増えることを許容するなら、roamswitch-server.service に環境変数 RS_PRN_HOOK=syscall を設定すると、システムコールのトレースポイントで記録できます(機械上のすべてのシステムコールが、少し遅くなります)。

記録は /var/lib/roamswitch/prn/(0700、ファイルは 0600、root 所有)に置き、通知、証拠のバンドル、MCP、イベント転送には出しません。見えないものがあります。システムコールを出さずに乱数を返す経路(glibc 2.41 以降の getrandom() が vDSO を使う環境)と、ユーザー空間で実行する RDRAND です。x86-64 では、カーネルパラメータ vdso=0 で、vDSO の経路を止められます。

状態は sudo roamswitch-prn-recorder status で見られます(動いているか、保存した量、この環境で見えない経路)。取り出すときは sudo roamswitch-prn-recorder export --output FILE [--since-hours N] [--pid PID] を使います。memento.prn/v1 の JSON Lines を、新しい 0600 のファイルに書きます(期限を過ぎた記録は含めません)。書き出したファイルには期限が適用されないので、使い終わったら、自分で安全に削除してください。sudo roamswitch-prn-recorder purge は、記録をいますべて消します。

記録は、そのマシンが作った TLS や SSH のセッションの秘密も含みます。root が奪われれば、期限の内の通信の鍵が作り直されます。保持時間と容量の上限は、必要な分だけにしてください。

← サーバー版ホワイトペーパー Linux版導入ページへ →