CrowdSecとPrometheus・Grafanaの統合:Linuxサーバーにおけるサイバー攻撃監視とアラート通知の実践

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

1. サーバーが悲鳴を上げているのにGrafanaはオールグリーンの怪

週半ばの深夜2時、チームが運用するNginxとAPIバックエンドの3ノードクラスタが突如として応答を停止しました。急いでGrafanaを開いて確認したものの、CPU使用率は35%未満、空きメモリは60%以上、ディスクI/Oも極めて平穏。Prometheusは沈黙を守り、ハードウェアリソース枯渇のアラートは1件も上がっていませんでした。

しかし、ユーザーからは504 Gateway Timeoutエラーの問い合わせが殺到していました。慌ててサーバーにSSH接続し、Nginxのログを確認したところ愕然としました。毎分9,000リクエスト以上が猛烈な勢いで押し寄せていたのです。システムは400以上の分散IPからWordPressの脆弱性スキャンやSQLインジェクション攻撃に晒されており、同時にSSHポートも総当たり(ブルートフォース)攻撃を受けてパンク寸前になっていました。

当時サーバーに導入していたFail2banは完全に処理能力の限界を超えていました。ギガバイト級に肥大化したログファイルに対する正規表現マッチングのせいでPythonプロセスが頻繁にスパイクを起こし、さらに厄介なことに、検知した悪意あるIPリストを残り2台のノードと共有する仕組みもありませんでした。

何より頭を抱えたのは、運用チームがアプリケーション層への攻撃状況をまったく把握できていなかった点です。「現在、悪質なIPがいくつ存在するのか?」「どの攻撃手法が大半を占めているのか?」「どれだけのペースでブロックできているのか?」といったリアルタイムのデータが完全に欠落していました。

2. システム監視とセキュリティツールの間に横たわるギャップ

今回のインシデントを振り返ると、従来の監視アーキテクチャには3つの明確な盲点が存在していました。

  • Node Exporterはハードウェアしか監視できない:PrometheusはCPU使用率を正確に把握できますが、毎分15,000件発生するHTTP 403エラーや、50回連続で失敗したSSHログイン試行などを検知することはできません。
  • Fail2banにはメトリクス出力機能がない:Fail2banは状態をプレーンログやローカルのSQLiteに保存します。データをPrometheusに取り込んで可視化するには、煩雑な連携スクリプトを自作するしかありません。
  • スタンドアロン動作で脅威インテリジェンスがない:Fail2banは攻撃者が自サーバーに何度も不正アクセスを仕掛けてきて初めて動作します。他のサーバーを何千台も攻撃し終えたばかりの危険なIPであっても、自サーバーにとっては「初対面」であるため、無防備に接続を受け入れてしまいます。

3. 検討した3つのアプローチとトレードオフ

セキュリティ関連データを1つのダッシュボードに集約するため、以下の3つのアプローチを比較検討しました。

アプローチ1:ログ解析スクリプトを作成しPrometheus Textfile Collectorへ出力

以前、1分ごとにcronでPythonスクリプトを実行し、「Failed password」の行数を集計して.promファイルに出力する方法を試したことがあります。手軽に実装できる反面、ログが数GBに達するとCPUリソースを大量に消費します。さらに、logrotateのタイミングでデータを取りこぼしやすいという欠点もありました。

アプローチ2:すべてのログをElasticsearch / Graylogクラスタへ集約

ログを集中管理型SIEMに集約する手法は、事後調査(フォレンジック)には非常に有用です。しかし、ELKクラスタを構築するには最低でも8GB〜16GBのRAMが必要となり、中小規模のインフラ環境にはコストがかかりすぎます。また、ログをクエリしてアラートを発報する仕組みは、Prometheusによる定期的なメトリクスプル方式に比べてレイテンシが大きくなりがちです。

アプローチ3:Fail2banをCrowdSecに置き換え、Prometheusメトリクスを有効化

CrowdSecもFail2banと同様にログを解析して不審な挙動を検知しますが、Go言語で開発されており非常に軽量です。本番環境でのメモリ消費量はわずか60MB〜80MB程度に収まります。そして何より、CrowdSecにはポート6060でPrometheusエンドポイントが標準搭載されています。追加のエクスポーターを導入することなく、ブロックされたIP数や攻撃シナリオ、ログ解析速度などのメトリクスをPrometheusから直接スクレイプ可能です。

4. CrowdSecとPrometheus・Grafanaの連携構築手順

以下は、筆者のチームがUbuntu 22.04 LTS環境で過去6ヶ月間にわたり安定稼働させている詳細な5つの設定手順です。

ステップ1:CrowdSecとRemediation Component(Bouncer)のインストール

CrowdSecエンジンおよび悪質なIPからのパケットを自動破棄するiptablesバウンサーをインストールします。

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt-get update
sudo apt-get install crowdsec
sudo apt-get install crowdsec-firewall-bouncer-iptables

インストール後、CrowdSecは実行中のサービス(Nginx、SSHなど)を自動検出し、対応するパーサーを自動的に読み込みます。

ステップ2:CrowdSecのPrometheus Metricsエンドポイントを有効化

デフォルトでPrometheusエンドポイントは有効化されていますが、バインドが127.0.0.1:6060に制限されています。設定ファイルを開きます。

sudo nano /etc/crowdsec/config.yaml

prometheusブロックを確認します。Prometheusが同居している場合は127.0.0.1のままで構いません。別サーバーから収集する場合はプライベートIPに変更してください。

prometheus:
  enabled: true
  listen_addr: 127.0.0.1
  listen_port: 6060

設定を反映するためにCrowdSecを再起動します。

sudo systemctl restart crowdsec

エンドポイントが正しくメトリクスを返しているかテストします。

curl -s http://127.0.0.1:6060/metrics | grep cs_

実行後、cs_alertsやcs_active_decisions、cs_reader_hits_totalといったcs_プレフィックスを持つメトリクスが表示されれば成功です。

ステップ3:PrometheusにScrape Jobを追加

Prometheusサーバーのprometheus.ymlを開き、メトリクス収集ジョブを追加します。

scrape_configs:
  - job_name: 'crowdsec'
    scrape_interval: 15s
    static_configs:
      - targets: ['10.0.1.15:6060']
        labels:
          instance: 'web-prod-01'

サービスを再起動せずにPrometheusの設定をリロードします。

curl -X POST http://localhost:9090/-/reload

ステップ4:Grafanaで可視化ダッシュボードを構築

CrowdSecは公式のGrafanaダッシュボードを提供しています。GrafanaのメニューからDashboards > Importへ進み、ID 13380または14115を入力し、先ほど設定したPrometheusデータソースを選択してインポートします。

監視上特に重要なパネルは以下の通りです。

  • Active Decisions:リアルタイムでブロックされているIPアドレス数。
  • Alerts by Scenarios:攻撃シナリオ別の内訳(ssh-bf、http-probing、crawl-non-staticsなど)。
  • Parser Performance:1秒あたりのログ読み込みおよび解析処理レート。

ステップ5:アラートルールの設定とアラート疲れ(Alert Fatigue)の解消

Prometheus用のアラートルールファイルcrowdsec_alerts.ymlを作成します。

groups:
  - name: CrowdSecSecurityAlerts
    rules:
      - alert: SuddenAttackSpike
        expr: sum(increase(cs_alerts[5m])) by (instance) > 20
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "サーバー {{ $labels.instance }} に対する攻撃の急増を検知"
          description: "過去5分間に20件を超える新規セキュリティアラートが記録されました。"

初期運用の段階では、IPが1件ブロックされるたびにTelegramボットへ通知を送る設定にしていました。その結果、ボットによる日常的なスキャンのたびにスマートフォンが終日鳴り続け、チーム全体が「アラート疲れ(Alert Fatigue)」に陥り、危うく通知チャンネルごとミュートしてしまうところでした。

この失敗から得た実践的な教訓は、単一のブロックイベントごとに通知するのではなく、「急激なスパイク(増加ペース)」を捉えてアラートを発報すべきだということです。例えば、5分間に20件以上のブロックが発生した段階でWarningを発令し、CPUの高負荷やHTTP 5xxエラー率の急上昇を伴う大規模な攻撃に発展した場合にのみCriticalを通知する設計が推奨されます。

Share: