コンテンツにスキップ

セキュリティ

NMS は、監視対象のネットワークの鍵を握っています。SNMP コミュニティ、SNMPv3 の認証情報、ポーリング 先サービスの API キーです。

Yagra のセキュリティモデルは、その事実から出発します。認証情報は保存するときに暗号化し、決して ログに出さず、通信経路では届く範囲を厳しく絞ります。

そのうえで、それ以外の攻撃面も小さく保ちます。非 root のコンテナ、内部だけに置くストア、検証した 外向きの宛先、そしてすべての変更に対する監査の記録です。

監視用の認証情報は、保存するときに二重に暗号化されます。まず各シークレットを、それぞれ専用の データ鍵で暗号化します。次にそのデータ鍵を、マスタ鍵(KEK)で包みます。

ですからデータベースが持っているのは、暗号文だけです。データベースのダンプを手に入れても、 認証情報は何も分かりません。

KEK は、マウントしたファイルです。環境変数の値ではありません。 YAGRA_KEK_FILE が運ぶのは 鍵そのものではなく、パスです。ですからシークレットが docker inspect やプロセスの環境から 漏れることはありません。同梱の compose ファイルは、専用ボリュームに鍵を一度だけ生成し、コアに 読み取り専用でマウントします。

消えない KEK が無い場合、コアは一時的な開発用の鍵に切り替えます。この鍵は再起動のたびに作り 直され、コアは強い警告を出します。その鍵で暗号化して保存した認証情報は、再起動をまたげません。 本番をこの状態のまま運用しないでください。

認証情報がログに出ることはありません。API から返されることもありません。ログ、API のレスポンス、 メトリクスのラベルのどこでも、同じように伏せられます。

3 つのアプリケーションコンテナはいずれも非 root ユーザーで動作します。

イメージ ユーザー 権限
yagra-core uid 10001 通常のプロセス以上のものはなし
yagra-poller uid 10002 NET_RAW のみ — raw ソケットの ICMP のための、バイナリに付与したファイルケーパビリティ
yagra-web nginx uid 101 非特権の nginx。コンテナポート 8080(80 ではありません)で待ち受け

CAP_NET_RAW を持つのは、ポーラーだけです。ICMP による死活監視に raw ソケットが要るからです。 それ以外のコンテナには与えません。

その結果、権限を絞ったポーラーは 1024 未満のホストポートを開けません。だからこそパッシブ リスナーは、大きい番号のコンテナポート(1514、1162)を使います。標準の小さい番号のポートは、 手前でマップするか転送します。詳しくはポートとファイアウォールを 見てください。

アップグレード用サイドカー(v0.2.2 以降)

Section titled “アップグレード用サイドカー(v0.2.2 以降)”

サーバー構成には、4 つ目のコンテナ yagra-updater が加わります。これだけが例外で、root で 動き、Docker ソケットをマウントします。リリースの導入とは、スタックを作り直すことだからです。

コアにソケットは渡しません。この分離こそが、サイドカーを別に立てている理由です。

ただしこれによって、Yagra の Admin ロールからホストの root へ至る経路が新しく生まれます。 歯止めは次の 4 つです。

  • イメージの取得元は、ホスト側の環境変数(YAGRA_UPGRADE_REPO)で固定されています。Admin が 入れられるのは、Yagra が公開したタグだけです。API の要求からレジストリを指定することは できません。
  • サイドカーが受け付ける命令は、あらかじめ決まった種類だけです。共有ボリュームの中身が実行される ことは一切ありません。 そこに置くのは、要求ファイル・ハートビート・アップロードされた アーカイブだけで、スクリプトは置きません。
  • アップグレードの要求には、manage-the-deployment(デプロイの管理)の権限が必要です。これを 持つのは Admin だけです。実行は監査に記録されます。MCP には出していません。
  • Settings ▸ Upgrade で機構を止めると、要求もレジストリへの外向き通信も止まります。ただし コンテナ自体は、構成から削除するまでソケットを持ち続けます。

この線を広げてしまう設定が 1 つだけあります。YAGRA_UPGRADE_ALLOW_BUNDLE です。これは アップロードされた docker save のアーカイブからの導入を許します。つまり対象が「Yagra が公開した タグ」から「アーカイブに入っている任意のイメージ」に変わります。

この設定は既定でオフです。そしてホスト側の設定であり、WebUI からは意図的に有効にできません。 UI はホストが与えた権限の中でしか動けず、権限そのものを広げることはできない、という線引きです。

この機能自体が不要な環境では、compose ファイルからサービスを削除してください。ただし各バージョン が自分のイメージに入っている構成を導入するので、次のアップグレードで戻ってきます。そうした 環境には、コマンドラインからのアップグレードのほうが向いています。

v0.3.3 からは、同じ形のサイドカーが監視拠点ごとに、既定で動きます。 Settings ▸ Pollers で 発行したキットが、その拠点の .env に COMPOSE_PROFILES=self-upgrade を書きます。これにより yagra-poller-updater が起動し、その拠点ホストの Docker ソケットを持ちます。上の 4 つの歯止めは 拠点でもそのまま効きます。命令がバスを通るぶん、次の 2 つが加わります。

  • 命令が運ぶのはバージョンタグだけで、リポジトリは含みません。 偽造できたとしても、入るのは Yagra が公開したポーラーのリリースだけです。
  • バスはポーラーにアップグレード命令の受信のみを許し、送信を許しません。ある拠点が別の拠点に 手を出すことはできません。

そのうえで意味するのは、中央のデプロイを支配する者は、全拠点の Yagra ポーラーを入れ替えられる ということです。キットを発行する前にチェックを外すか、あとからその拠点の .env で COMPOSE_PROFILES の値を空にすれば、その拠点でソケットを持つコンテナは 1 つも動きません。 v0.3.3 より前にキットを渡した拠点は、キットを再発行して渡すまで影響を受けません。

5 つのストア(PostgreSQL、Redis、VictoriaMetrics、VictoriaLogs、ClickHouse)と NATS のバスは、 すべて内部の Docker ネットワークの中にあります。同梱の compose ファイルは、どれもこれらを外に 公開しません。スタックの外から届く必要も一切ありません。そのままにしてください。

バスには特に注意が要ります。ジョブのメッセージは、デバイスの認証情報を平文で運びます。 コアから、それを使うポーラーへ向けてです。単一ホストの中なら、この通信が内部ネットワークを出る ことはありません。

しかしリモート拠点のポーラーを動かし始めた瞬間から、話が変わります。バスのポートを公開してよいの は、同梱の TLS + 認証の設定を整えた場合だけです。信頼境界をまたいで平文の NATS :4222 を 晒してはいけません。

さらに守りを固めることもできます。各ポーラーがバスに要求できる範囲まで絞る方法です。

NATS Auth Callout を有効にすると、コアがバスの認可サービスとして動きます。接続してくる ポーラーごとに、自分の割当サブジェクトと自分のプールの通信だけに限った、短い有効範囲の識別情報を 発行します。

こうすると、侵害されたポーラーは他の拠点のジョブを購読できません。ですから他拠点の認証情報を 受け取ることもできません。設定方法とリモートポーラーの構造は、 分散ポーリングで扱っています。

WebUI はホストのポート 443 で HTTPS 提供されます。平文のリスナーはありません。

WebUI が運ぶのは、ログインパスワード、Bearer のセッショントークン、そして暗号化される前のデバイス 認証情報です。以前は、これらが既定のままだと平文でネットワークを渡っていました。今は、何もし なくても手に入る側が安全な構成になっています。

証明書の正本は PostgreSQL にあり、他のすべての秘密と同じ KEK で二重に暗号化されます。コアがそれを、 web コンテナの読むボリュームへ書き出します。

何も取り込んでいない初回起動では、コアが自己署名の証明書を作ります。対象はループバックと コンテナのホスト名です。ですからブラウザは警告を出しますし、名前も一致しないのが普通です。 コンテナの中からは、実際に使われるアドレスを知りようがないからです。

設定 ▸ TLS 証明書で、正式な証明書を取り込んでください。あるいは実際に使う名前を指定して、 自己署名証明書を作り直せます。秘密鍵が API から返ることはありません。証明書のほうはダウンロード できるので、Prometheus の ca_file、curl --cacert、OS のトラストストアに渡せます。

取り込んだ証明書は、何も再起動せずに数秒で有効になります。有効期限は Yagra ヘルスが報告します (Prometheus の yagra_web_tls_expires_in_days にも出ます)。

手前に置いた外部のリバースプロキシやロードバランサが、すでに HTTPS を終端している場合は YAGRA_WEB_TLS=off を設定してください。平文のリスナーに戻す手段は、これだけです。

コア自身の API ポート(8080)は、平文のまま LAN に公開されています。 これは見落としでは なく、意図した順序です。

まだ信頼されていない証明書を入れるのと同じアップグレードでこのポートを閉じると、Prometheus の scrape も API スクリプトも一斉に落ちます。原因が 2 つ重なって、切り分けできなくなります。

ですから、まずそれらのクライアントを、信頼できる証明書のある https://<host>/api/v1/… へ移して ください。そのあとで YAGRA_API_BIND=127.0.0.1 を設定し、ポートをネットワークから外します。 今どちらの状態かは、設定 ▸ TLS 証明書に出ます。8080 を素のまま、信頼できないネットワークへ 公開しないでください。

対話的なアクセスはセッションベースです。POST /api/v1/auth/login がユーザー名とパスワードを Bearer のセッショントークンと交換し、以後の REST リクエストはすべてそれを提示します。

  • 総当たり対策。 ログインのエンドポイントは、失敗が続くとアカウント単位で待ち時間を指数的に 伸ばします。加えて、全体の試行レートにも上限をかけます。狙いは 2 つで、パスワードの連続推測を 防ぐことと、パスワードのハッシュ計算に使わされる CPU を抑えることです。
  • 有効期限。 セッションは、放置した時間と、発行からの絶対的な寿命の両方で切れます。
  • 失効。 ログアウトすると、トークンはサーバー側で無効になります。ユーザーの無効化、降格、削除、 パスワードのリセットも、そのアカウントの生きているセッションを即座に無効にします。侵害された アカウントへの管理者の対応が、発行済みのトークンをその場で断ち切ります。
  • 初回起動。 YAGRA_ADMIN_PASSWORD を設定していない場合、コアは admin 用の一度限りの パスワードをランダムに作り、ログに 1 回だけ出します。誰もが知っている既定パスワードは存在 しません。

高可用性のペアでは、署名鍵をファイルでマウントできます(YAGRA_SESSION_KEY_FILE)。これを 設定すると、セッションは状態を持たない署名付きトークンになります。

このトークンはペアのどのコアでも検証でき、コアの再起動やフェイルオーバーを越えて有効です。 ですからフェイルオーバーで全員がログアウトされることはありません。失効はこれまでどおり効きます。 失効したトークンは記録され、どのコアでも拒否されます。

人が操作しないクライアント向けに、管理者は設定 ▸ API トークンから長期間使える API トークン (yat_ で始まります)を発行できます。トークンには次の制限がかかります。

  • 使える入口。 トークンが届く先は、MCP のエンドポイント(/mcp)か、REST API か、その両方 です。発行するときに選びます。この項目ができる前に作られたトークンは mcp だけを持ちます。 ですからアップグレードで、既存の認証情報の権限が勝手に広がることはありません。

  • 所有アカウント。 実効のロールは、トークン自身のロールと、所有者の現在のロールの、低いほう です。ですから所有者を降格すれば、その場で狭まります。アカウントを無効化したり削除したりすれば、 トークンも失効します。

    人が見ていないトークンは、サービスアカウントに所有させてください。サインインできない、 機械のための識別子です。こうすると認証情報が個人に依存しなくなり、1 つのスイッチでそのアカウント が持つすべてを止められます。

  • ロールに関わらず、トークンにできないことが 2 つあります。ユーザーの管理と、サインイン中の アカウントを特定するエンドポイントの利用です。前者が特に重要です。自分の後継を発行できる認証 情報は、元の失効を生き延びてしまいます。そしてそれは、退職処理がまさに見落とすものです。

  • 有効期限を任意で設定できます。同じ画面から、いつでも失効させられます。

  • 生のトークンが表示されるのは、作成時のちょうど 1 回だけです。保存されるのはハッシュだけです。

  • トークンの対象はフリート全体です。グループを限定したトークンは発行できません。

  • 発行と失効、そしてトークンで行われたすべての書き込みは、監査ログに残ります。記録は所有アカウント とトークンの両方で行われます(svc-ci (token:grafana) のような形です)。

運用担当が Yagra に URL を指定できる機能は、いくつかあります。そのどれもが、リクエストの実際の 行き先を検証します。

  • URL 監視は、HTTP(S) 以外のスキームを拒否します。また、届いてはいけない宛先も拒否します。 ループバックや、リンクローカル(クラウドのメタデータアドレス 169.254.169.254 を含みます)が それにあたります。権限の昇格につながりうるからです。

    検証は、設定したときとポーリングのときの両方で行います。その間に DNS が変わりうるからです。 リダイレクトも、同じ方針で 1 ホップずつ検証し直します。

    なお、通常のプライベートアドレス空間は許可されたままです。プライベートな設備を監視することこそ、 NMS の目的だからです。

  • Webhook の通知先も、設定したときに同じ方法で検証し、送るときに検証し直します。外向きの クライアントは、リダイレクトを一切追いません。

  • PagerDuty への送信は、公式のイベントエンドポイント(events.pagerduty.com / events.eu.pagerduty.com)に固定してあり、HTTPS だけを使います。紛らわしいホスト名や、 平文 HTTP の URL は、設定のときに拒否されます。Jira Service Management も同じように api.atlassian.com に固定されています。

  • Cisco Meraki 監視は、完全に読み取り専用です。クライアントは GET しか発行せず、リダイレクト を追わず、ページをめくるたびにホストを検証し直します。

状態を変える API 操作は、すべて記録されます。ノードの作成、認証情報の入れ替え、アラートの確認 (ACK)、トークンの発行、メンテナンスウィンドウの開始などです。記録には、実行したユーザー、 変更の内容、日時が残ります。MCP の書き込みツールも、トークンの識別情報のもとに同じ記録を残します。

例外が 1 つあります。自分の WebUI の設定の保存(PUT /api/v1/preferences)は記録しません。 インベントリのツリーで閉じたフォルダ、列の幅、ピン留めのみ などの保存です。これは本人の画面しか 変えません。しかもフォルダを開け閉めするたびに送られます。

記録は設定 ▸ 監査ログで見られます。閲覧には監査の権限が必要です。既定でこれを持つのは、 Admin(管理者)ロールだけです。

データがシステムの外に出る経路

Section titled “データがシステムの外に出る経路”

何も設定していない新規インストールは、外向きの通信を一切行いません(イメージの取得は除きます)。 外へ出る経路はすべて、機能ごとに自分で有効にするものです。

  • 通知チャネル — Webhook、メール(SMTP)、PagerDuty、Jira Service Management — は、設定した 宛先へアラートの内容を送ります。

  • 転送は、受け取った syslog・トラップ・フローを、設定した外部コレクタへ中継します。正規化した イベントやフローの行を BigQuery へ流すこともできます。

  • AI 根本原因分析は、既定で無効です。プロバイダを設定しなければ、クライアントも認証情報も 外向きの通信も存在しません。

    有効にすると、インシデントの文脈が、設定した 1 つのプロバイダへ送られます。認証情報が含まれる ことは決してありません。Vertex AI を選んだ場合、送り先は自分の GCP プロジェクトの中に留まります。

  • MCP のツールの結果 — インベントリ、ステータス、メトリクス、イベント — は、/mcp につないだ AI クライアントへ渡ります。MCP を有効にするということは、監視データを読ませてよいと、その クライアントを信頼するということです。

完全な一覧は、ポートとファイアウォールの外向き通信の表にあります。 すべての宛先、プロトコル、そしてどのプロセスが外へつなぐかが載っています。

脆弱性の疑いは、GitHub リポジトリから報告してください。 修正が用意される前に、公開の issue で攻撃の詳細を書くことはお控えください。