概要 — 何をする画面か
セキュリティ診断 は、登録済みのサーバーに対して ルートキット痕跡スキャンとシステムハードニングスキャン を実行し、それぞれの結果を 0〜100点のスコアと A〜F の評価 で採点して、フリート全体のセキュリティ体制を1画面に表示します。すべてのチェックには「何を/なぜ/どう直すか」の解説が付いており、失敗であり、かつ深刻度が致命的な検出結果(fail + crit) は自動的に 問題解決 のケースへ 昇格します(重複は排除され、後のスキャンで合格すれば自動的に解消されます)。
主要な構成要素:
- プローブ
infr_seccheck_probe.cjs— 対象ホスト上で ローカルかつ読み取り専用 で動作する Node スクリプトです(認証情報は不要)。36項目のチェックを実行し、JSON を出力します。 - 正本カタログ
App\Support\SecurityCheckbook— 9つのカテゴリ、チェックキー、重み、深刻度を所有します(ロケール非依存でキャッシュ安全)。解説テキストはresources/lang/{ko,en}/seccheck.phpにあります。 - エンジン
App\Services\Security\DiagnosticsService— 取り込み → 採点 → ケースの昇格・解消 → ダッシュボード集計。 - 保存先
infr_sec_scan(マイグレーション000766)— 1行 = 1ホストのスキャン。findingsは JSON としてインラインに保存されます。
プローブを root で 実行すると、
/etc/shadow・sudoers・sshd -Tなどの特権チェックが有効になります。root でない場合、特権チェックはskipに降格し、スコアの分母から除外されます(減点なし)。
こんなときに使う
- 新しいサーバーを本番に受け入れる前の ハードニング基準の検証。
- 侵害が疑われるホストにおける ルートキット/バックドアの痕跡 の迅速なトリアージ(隠しプロセス、
ld.so.preloadへのインジェクション、プロミスキャスモードの NIC など)。 - 定期的なコンプライアンスのリズム — 日次の cron でプローブを実行し、API レポーター 経由でスコアを送信して、トレンドを自動的に蓄積します。
- 監査対応 — EOL の OS で稼働しているホスト、未適用のセキュリティ更新があるホスト、root の SSH ログインを許可しているホストを一目で把握できます。
画面構成
| エリア | 内容 |
|---|---|
| KPI ストリップ | フリート平均スコア · スキャン済みホスト数 · 未解決の致命的ケース · 失敗チェックの総数 · 最終スキャン時刻 |
| 評価の分布 | A/B/C/D/F のホスト数のヒストグラム |
| カテゴリ別スコア | 9つのカテゴリのロールアップ(フリート平均、カタログ順、トーン色) |
| スコア推移 | 直近60件のスキャンを時系列で表示 |
| 評価別ホスト | ホストカードのグリッド — スコアの低い(リスクの高い)順に並びます。各カードには評価、pass/warn/fail/crit の件数、取り込み経路、スキャン時刻が表示されます。クリック → レポートモーダル |
| レポートモーダル | 総合スコアと評価 + サマリー(OS/カーネル)+ チェックごとの詳細(何を/なぜ/対処/根拠)+ 昇格したケースへのリンク |
上部ツールバー: [スキャン実行](複数サーバーのリモート実行)· [結果を貼り付け] · 更新 · 自動更新トグル · 全画面。
使い方
4つの取り込み経路
infr_sec_scan.source は、スキャンをどのように受け取ったかを分類します:
| source | 入口 | 方法 |
|---|---|---|
emulate |
[スキャン実行] モーダル | インスタンスを選択 → プローブをリモートに配置して実行します。instance_id を伴う貼り付けやリモートエミュレートの適用も emulate として記録されます |
paste |
[結果を貼り付け] モーダル | 対象ホスト上で直接生成したプローブの JSON を貼り付けます(インスタンスとの紐付けなし) |
api |
POST /api/v1/security/scan |
各ホストの cron がプローブを実行 → API キーで送信します(レポーター 経路) |
manual |
フォールバック | source の値が有効でない場合の既定値 |
1) 複数サーバーのリモート実行 — 非同期ポーリング
同期的にすべてを実行すると、多数のホストにまたがるため PHP の実行時間を超過します。そのため 登録 → 撃ちっぱなしのワーカー → ポーリング という流れになっています。
- [スキャン実行] → 対象インスタンスにチェックを入れます(それぞれ到達可能/到達不可が表示されます)。
POST /security/diagnostics/run→ 進捗レコードを作成し、run_idを即座に返します(Cache、TTL 30分)。- フロントエンドは
POST /security/diagnostics/run-worker/{runId}を 待たずに 呼び出します → ワーカーはset_time_limit(0)とignore_user_abortのもとで全ホストを順番に実行します(1台が失敗しても残りは止まりません)。 - フロントエンドは
GET /security/diagnostics/run-status/{runId}をポーリングして進捗(done/total/ok)を更新します。
到達不可 と表示されるインスタンスは
run_reportコレクターのノードと一致しないため、リモートでは実行できません。ホスト上で直接プローブを実行し、[結果を貼り付け] から投入してください。リモート実行の配線については リモートエミュレート を参照してください。
2) 貼り付け/JSON の取り込み
対象ホストでプローブを実行し、その JSON を貼り付けます。
# 対象ホスト上で(推奨: root)
node infr_seccheck_probe.cjs --min
--min は1行の JSON を出力します。出力の形:
{
"host": "web-01",
"generated_at": "2026-08-13T03:30:00Z",
"os": "Rocky Linux 9.3",
"kernel": "5.14.0-362.el9",
"root": true,
"checks": [
{ "key": "ssh_root_login", "category": "ssh", "status": "fail", "severity": "crit", "detail": "PermitRootLogin yes", "evidence": "/etc/ssh/sshd_config:32" },
{ "key": "svc_firewall", "category": "services", "status": "pass", "severity": "crit", "detail": "firewalld active" }
],
"summary": {}
}
受け取るのは POST /security/diagnostics/ingest です。インスタンスを紐付けると source=emulate として記録され、紐付けないままだと source=paste として記録されます。
3) API レポーター(自動化)
各ホストの cron がプローブを実行し、結果を InfraGuard へ送信します。API キーは 管理 ▸ APIキー で発行してください。キーには scan.write スコープが必要です。
30 3 * * * node /opt/infr/infr_seccheck_probe.cjs --min | curl -sS -X POST https://infraguard.example.com/api/v1/security/scan -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" --data @-
レスポンス(201):
{ "ok": true, "scan_id": 812, "host": "web-01", "score": 74, "grade": "C" }
採点 — SecurityCheckbook
スコアは、各チェックの 重みとステータス による加重比率です。
ステータス係数(STATUS_FACTOR) — 対象になるのはこれだけで、skip/info は分母から除外されます:
| ステータス | 係数 |
|---|---|
pass |
1.0 |
warn |
0.6 |
fail |
0.0 |
skip · info |
除外 |
深刻度の乗数(SEV_MUL) — 失敗したときの含意が重いほど、スコアを大きく動かします:
| fail_severity | 乗数 |
|---|---|
crit |
1.6 |
warn |
1.0 |
info |
0.5 |
チェックごとの重みは w = check_weight(1–3) × severity_multiplier、総合スコアは = round(100 × Σ(w × status_factor) / Σw) です。カテゴリ別スコアも、カテゴリ単位で同じ式を使います。
評価とトーン:
| スコア | 評価 | トーン |
|---|---|---|
| ≥ 90 | A | good |
| ≥ 80 | B | good |
| ≥ 70 | C | warn |
| ≥ 60 | D | warn |
| < 60 | F | crit |
(評価は A/B/C/D/F、トーンは good ≥ 80 · warn ≥ 60 · crit < 60 です。)
9つのカテゴリ · 36項目のチェック
各カテゴリには表示用の重みとアイコンがあります。実際の採点には、上記の チェックごとの重み × 深刻度の乗数 を使います。
| カテゴリ | ラベル | チェック数 | 主なチェック(失敗時の含意) |
|---|---|---|---|
rootkit |
ルートキット/マルウェア | 6 | rk_known_files·rk_ld_preload·rk_hidden_proc(crit) |
patch |
パッチ/更新 | 3 | pt_security_updates·pt_eol_os(crit) |
ssh |
SSH 設定 | 5 | ssh_root_login·ssh_empty_pass(crit) |
accounts |
アカウント/権限 | 5 | acc_uid0·acc_empty_pass(crit) |
network |
ネットワークの露出 | 3 | net_legacy_services·net_db_exposed(crit) |
filesystem |
ファイルシステムの権限 | 5 | fs_shadow_perm(crit)·fs_world_writable·fs_suid(warn) |
hardening |
システムハードニング | 5 | hd_selinux·hd_aslr·hd_syncookies(warn) |
services |
サービス/ファイアウォール | 2 | svc_firewall(crit)·svc_unnecessary(info) |
logging |
監査/ロギング | 2 | log_auditd·log_syslog(warn) |
未知のキーは other カテゴリに吸収されます。各チェックの「何を/なぜ/対処」のテキストは、seccheck.check.* からレポートモーダルに描画されます。
致命的な検出結果 → 問題解決への昇格
取り込みのたびに、reconcileCases() がそのホストのケースを突き合わせます。
- 昇格するのは
status = failかつseverity = critの検出結果だけです。 重複排除キーは内部のfingerprintです。 - 未解決のケースが既にあれば
occurrence_count++とlast_occurred_atを更新します。なければ 新しいケースを作成します:infr_incident(area=security,source=detection,entity_type=seccheck,entity_key=host,severity=crit,status=open)とinfr_incident_event(kind=created)です。 - 自動解消: 以前は未解決だったケースが今回のスキャンで fail+crit ではなくなった場合(修正済み、または検出されなくなった場合)、
status=resolvedとresolved_at、および自動解消のメモが設定されます。
昇格したチェックはレポートモーダルに「問題解決へ昇格」バッジと [ケースを表示] リンク付きで表示されます。ケースは セキュリティ ▸ 問題解決 のボードで対応します。
スキャンの削除(
POST /security/diagnostics/delete)は、昇格済みのケースを 削除しません — 両者は独立しています。
データソース
| ソース | 役割 |
|---|---|
infr_sec_scan(000766) |
スキャン結果(ホストごとに1件)。score·grade·pass/warn/fail/crit/total_count·category_scores·summary·findings の JSON。インデックスは (host,scanned_at)·(instance_id,scanned_at)·grade·score |
infr_instance |
対象インスタンス(alias/hostname から host → instance_id を解決します) |
infr_incident / infr_incident_event |
昇格した致命的ケースとそのタイムライン |
infr_api_key |
API レポーターの認証(スコープ scan.write) |
| Cache | ダッシュボードの集計を20秒キャッシュ(infr:sec:diag:build)· 非同期実行の進捗レコード(TTL 1800秒) |
infr_seccheck_probe.cjs |
元となるプローブ(ローカル、読み取り専用) |
- 集計の規律: 直近500件のスキャンのうち、ホストごとに最新の1件だけを残し(重複排除)→ フリート一覧、平均、カテゴリのロールアップ、推移、未解決の致命的件数を算出します。
- プローブのカテゴリと深刻度はヒントに過ぎず、サーバー側が
SecurityCheckbookによって 正本として再正規化します。
アクセス権限
- 閲覧: セキュリティの役割領域へのアクセスが必要です(現行のティアでは staff = 5 / admin = 10。demo = 1 はセキュリティ領域を見られません)。ダッシュボードとレポートを閲覧できます。
- 書き込み(実行/取り込み/削除): セッションの
infr_user_level >= 2が必要です(canWrite())。それ未満ではrun·ingest·deleteは 403(seccheck.toast.forbidden)を返します。 - API レポーター:
infr.apikeyミドルウェアに加えて、scan.writeスコープを持つキーが必要です。ない場合は 403forbidden_scopeになります。 - 監査: すべての書き込みが
infr_audit_logに記録されます —sec.diag_run(対象数)、sec.diag_ingest(scan_id·score·source)、sec.diag_delete(スキャンの id)。監査ログ で確認してください。
レベルモデル全般(infr.level:N ミドルウェア)については、RBAC レベル を参照してください。
トラブルシューティングと FAQ
Q. [スキャン実行] でインスタンスが「到達不可」と表示されます。
run_report コレクターのノードと一致しないため、リモート実行の配線がありません。ホスト上で直接プローブを実行し、[結果を貼り付け] から投入してください。
Q. 「No checks found」エラーが出ます。
貼り付けた JSON に checks(または items)配列がないか、要素に key がありません。infr_seccheck_probe.cjs --min の出力をそのまま貼り付けてください。
Q. 失敗件数は少ないのに、スコアが低いです。
深刻度の乗数のせいです。crit の失敗1件は warn の1.6倍の重みを持ちます。カテゴリ別スコアとレポートモーダルで、どの検出結果がスコアを押し下げているのかを確認してください。
Q. 特権チェックがすべて skip になります。
プローブが root 以外のアカウントで実行されました。/etc/shadow・sudoers・sshd -T などのチェックには root が必要です。skip は分母から除外されます(減点なし)が、完全な診断のためには root で(またはゲートウェイ経由で)実行してください。
Q. 実行に時間がかかります。ブラウザを閉じてもよいですか。
はい。ワーカーは撃ちっぱなしで動作し、リクエストが中断されても ignore_user_abort のもとで継続します。開き直して run-status をポーリングするか、ダッシュボードに評価が現れてから読むだけでも構いません。進捗レコードは30分間保持されます。
Q. ケースが勝手に消えました。 その後のスキャンで、その検出結果が fail+crit として報告されなくなった場合(修正済み、または検出されない場合)、エンジンがケースを自動的に解消します。これは想定どおりの動作です。
関連項目
- セキュリティ ▸ 問題解決 — 昇格した致命的ケースのワークフロー
- リモートエミュレート — 複数サーバーへのプローブ配置と実行の配線
- APIキー — レポーター経路のキー(スコープ
scan.write) - 資産 ▸ インスタンス — 対象となる OS インスタンスの台帳
- セキュリティ ▸ 概要 — 防御ファネルの全体像
- 監査ログ — 実行/取り込み/削除の履歴
