rust-proxmoxmcp について
rust-proxmoxmcpは、複数のProxmox VEクラスターを管理するRust製MCPサーバーです。スコープ付きのインベントリや運用ツールに加え、保護チェックと監査ログを備えた計画・承認・適用の変更フローを提供します。
主な機能
- 01マルチクラスターのインベントリとアクセススコープ
- 02ゲスト・ノード・ストレージ・ファイアウォールの読み取り
- 03変更セットの計画・承認・適用
- 04保護対象ゲストの確認と期限付き免除
- 05任意のPIIマスキング付き構造化監査ログ
- 06クラスターCAピンニングと処理中操作の復旧
活用シーン
- 01クラスター間のVMとコンテナの状態確認
- 02ストレージ、バックアップ、ファイアウォール設定の調査
- 03承認済み変更のProxmoxゲストへの適用
- 04保護されたインフラ上の操作の監査
MCP 対応
MCPサーバーは、Proxmox VEクラスター向けにスコープ付きの読み取りツールと変更管理ツールを公開します。破壊的変更は計画・承認・適用の手順を踏み、承認はプレビューにひも付き、人間の承認者トークンを必要とします。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm ghcr.io/mechubsec/rustproxmoxmcp:0.12.0ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/mechubsec/rustproxmoxmcp:0.12.0stdioツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
rustproxmoxmcp
複数のProxmox VEクラスターに対応する単一のRust MCPサーバー
mechubプロジェクト — 主権的なネットワークセキュリティ自動化
非公式/コミュニティプロジェクトです。 これは独立したコミュニティプロジェクトであり、Proxmox Server Solutions GmbHとの提携や同社による推奨を主張するものではありません。製品名および商標は、このソフトウェアが相互運用するシステムを特定する目的でのみ使用しています。
ステータス:0.11.0 — 1つの不足を除き、ツール群が完成
呼び出し可能なツールは51個です。読み取り専用が32個、lowが18個、そして唯一のdestructiveエントリーポイントとしてapply_proxmox_change_setがあります。さらに7つの名前、delete_vm、delete_container、delete_snapshot、delete_backup、delete_iso、restore_backup、rollback_snapshotは認可スコープであり、ツールではありません。トークンは名前でこれらの権限を付与し、plan_proxmox_destroy経由で呼び出します。そのため、KNOWN_TOOLSには58個のエントリーがあります。
未対応の機能
execute_vm_command。 意図的に未対応です。設計仕様では、mecmcp-policyがコマンドの対象に対する許可/拒否ルールセットをコンパイルすることを条件としており、まだ接続されていません。すべてのゲスト内で任意のコマンドを実行することは、ツール呼び出しによるリモートコード実行です。ポリシーエンジンとともに提供するか、提供しないかのどちらかです。
残る課題は#57で追跡しています。サードパーティ製のproxmox-mcpが提供するその他の機能はすべて、こちらにも同等のものがあります。引数の形式が変更された箇所も記載しているdocs/MIGRATING-FROM-PROXMOX-MCP.mdを参照してください。
変更管理
破壊的な操作は、計画 → 承認 → 適用の手順を経由します。計画ではサーバーが生成したプレビューを表示して操作を記録します。承認では(owner, device, expected fingerprint, actions)に対する計画ダイジェストを紐付けます。適用時にはゲストのフィンガープリントを再確認し、変更されていた場合は拒否します。
誤解されやすいため、明確に述べておくべき点が2つあります。
-
承認はプレビューに紐付けられます。 mecmcp 0.23.0以降、承認ダイジェストには計画に加えて、保存されたプレビューのハッシュも含まれます。そのため、承認者は操作とそのパラメーターだけでなく、実際に提示された正確なテキストにも同意することになります。承認が存在する場合、コーディネーターは後続の書き込みでプレビューが差し替えられたり削除されたりすることを拒否します(#56)。
これは、このセクションの以前の版に記載されていた内容を逆転させるものです。0.23.0より前は、プレビューは独自のハッシュとともに保存されていましたが、ダイジェストには含まれていませんでした。そのため、承認者が同意するのは操作であり、プレビューはその操作から単に生成されていました。
-
--lab-modeは保護の上書きであり、全面的な免除ではありません。 保護対象のゲストに必要な上書きを提供するため、ラボモードのサーバーでは、保護対象のゲストは作成時に別のプリンシパルによる承認なしで承認されます。一方、通常のゲストには引き続き別のプリンシパルが必要であり、自己承認は拒否されます。この逆転に戸惑う人もいます。
--waivers-file(デフォルトは/etc/proxmoxmcp/waivers.json、モード0600、サービス所有)は、期間を限定したオペレーターの免除を保持します。どちらの上書きもツール呼び出しの外部から行われます。意図的にgrant_waiverツールもforce引数もありません。呼び出し側が指定できる上書きは、上書きとは言えないからです。
- 免除によって解除されるのは保護のみであり、2人目の承認者は不要になりません。 一致する免除があれば、保護対象ゲストに対する計画は保護ゲートを通過できます。ただし、生成された変更セットは通常どおり計画 → 承認 → 適用のフローを経由し、別の人間の承認者(または承認も免除する唯一の方法である
--lab-mode)を引き続き必要とします。免除の理由とチケットは保存されたプレビューに表示されるため、承認者は保護が解除された理由を確認したうえで、承認も行うかどうかを判断できます。
waivers.json スキーマ
{
"version": 1,
"waivers": [
{
"cluster": "pve3",
"vmid": 905,
"until": "2026-12-31T23:59:59Z",
"reason": "decommission per CHG-1234",
"ticket": "CHG-1234",
"ops": ["destroy_guest"],
"principal": "ops-token"
}
]
}
-
opsは必須で、空であってはなりません。 バージョン 1 の免除エントリーにopsフィールドがない場合、またはリストが空の場合、読み込み時に拒否されます。どの操作も対象にしない免除がある状態では、サーバーは起動しません。これは、opsフィールドがまったくなかった MEC-447 以前の免除ファイルからの、意図的にフェイルクローズする破壊的変更です。アップグレードする前に、各エントリーを対象とする操作を正確に指定して再追加してください。 -
principalは任意です。 設定すると、そのトークン名にのみ免除が適用されます。設定しない場合は、すべての呼び出し元に適用されます。これは操作を計画するトークンの名前であり、承認または適用するトークンではありません。2 人ルールでは承認者が計画者とは別のプリンシパルである必要があるため、approve_proxmox_change_setとget_proxmox_change_setは、変更セットに記録された計画者に対して免除を評価します。そのため、principalを指定した免除では、計画者以外の呼び出し元による適用実行は拒否されます。これは意図されたフェイルクローズの動作であり、バグではありません。 -
opsのop文字列には、免除が対象とするパスに応じた名前を指定する必要があります。plan_proxmox_destroyで計画された破壊的操作:その呼び出しに渡すop引数(destroy_guest、delete_snapshot、rollback_snapshot、delete_backup、delete_iso、restore_backup、migrate、update_vm_config)。- 新しい VMID を対象とする復元(
restore_backup_new_vmid):固定文字列restore_new_vmid。新しい VMID ではなく、免除で保護するアーカイブ所有者のゲストを指します。 - サービスを中断する
low階層のツール(例:stop_vm、stop_container、reboot_vm):ツール自体の名前。 plan_ha_rule_changeで計画された HA ルールの変更:ha_rule_にその呼び出しのopを続けたもの(ha_rule_create、ha_rule_update、ha_rule_delete)。plan_firewall_changeで計画されたファイアウォールの変更:firewall_にオブジェクト名と操作を続けたもの(firewall_rule_create、firewall_options_update、firewall_ipset_entry_delete)。
操作名を誤記したり、対象の操作を指定しなかったりした免除は、何にも適用されません。その呼び出しは、免除が存在しない場合と同様に拒否されます。
-
approve_proxmox_change_setには、人間の承認者のトークンが必要です。 サーバーは呼び出し元のトークンのactor_typeを mecmcp に渡します。mecmcp は、agentまたは帰属情報のない(stdio)呼び出し元からの承認を拒否し、actor_type: humanのみを承認できます。rust-proxmoxmcp token add ... --actor-type humanを使って承認者のトークンを発行してください。actor_typeは発行時にオペレーターが申告するクレームであり、サーバーが証明するものではありません。humanとタグ付けしたトークンを LLM エージェントに渡すと、このゲートの目的が失われます。
実装済みの機能
- マルチクラスターインベントリ: 1 台のサーバーで複数のクラスターを管理します。各クラスターには独自の API トークンと保護ポリシーが設定されます。
- 2 段階の認可:
- ステージ 1(カタログ呼び出し前):Bearer トークンの検証、ツールとクラスターのスコープ確認。
- ステージ 2(ゲストを指定するツールのみ):ゲストの解決、権限の評価(VMID 範囲、タグ、プールセレクター)、およびフェイルクローズ保護。
- 保護の統合: ゲストが
protected_vmidsまたはprotected_tagsのタグのいずれかに該当する場合、そのゲストは保護されます。保護対象のゲストに対しては、免除またはラボモードによるオーバーライドが適用されない限り、すべての破壊的操作ツールおよびサービスを中断するツールが拒否されます。(読み取りツールでは、通常どおり保護対象のゲストを参照できます。) - ゲストを指定するツールは、呼び出し元からノードを受け取りません。 ゲストは呼び出しごとに解決され、ゲストは移行されるため、適用時にも再度解決されます。ノード、ストレージ、タスクを対象とするツール(
get_node_status、get_node_updates、get_storage、list_backups、list_isos、list_templates、list_tasks、get_task_status、download_iso、get_node_firewall_rules、get_node_firewall_options)ではnodeを受け取ります。対象となるのはノードであり、ノードを解決する元となるゲストが存在しないためです。重要な例外はcreate_vm/create_containerです。これらはゲストを指定しますが、ゲストはまだ存在しないため、呼び出し元がノードを指定します。ゲストを指定する呼び出しが誤ったホストに到達する可能性があるのは、この場合だけです。stop_taskは意図的にこの一覧に含めていません。UPID からノードを読み取るためです。 - カタログ駆動のディスパッチ: すべての読み取りツールの HTTP メソッド、パステンプレート、クエリフラグ、型フィルターは、
catalog.rsで一度だけ宣言されます。 - 実行中処理の復旧: タスクハンドルを持つ
Applying状態の変更セットは起動時に再プローブされるため、適用処理は未解決のまま残らず、再起動後に解決されます。 - SIGHUP による再読み込み:
systemctl reloadを実行すると、実行中の呼び出しを中断せずにclusters.jsonがその場で再読み込みされます。再読み込みに失敗した場合はログに記録され、以前のスナップショットが保持されます。 - クラスターごとの CA ピンニング: 各クラスターで
ca_pem_pathを指定できます。どのレイヤーにも insecure-skip-verify はありません。そのため、プライベート CA を使用するクラスターでは、その CA をインストールし、証明書の対象に含まれる名前でアクセスする必要があります。 - 監査ログ: オプションで PII を秘匿化(HMAC キー方式または削除)できる、JSON 構造化ログです。すべてのツール呼び出しで、クラスター、ゲスト、ティア、保護状態が記録されます。
32 個の読み取りツール
| ツール | スコープ | 説明 |
|---|---|---|
get_cluster_status | クラスター | クォーラムとノードのメンバーシップ |
get_nodes | クラスター | ステータスとリソース合計を含むすべてのノード |
get_node_status | ノード | 1つのノードの詳細なステータス |
get_node_updates | ノード | 保留中の apt パッケージ更新(任意の Sys.Modify が必要です。以下を参照) |
get_vms | クラスター | ノード、ステータス、タグを含むすべての QEMU ゲスト(ページ分割:offset/limit、デフォルト 500、最大 700) |
get_containers | クラスター | ノード、ステータス、タグを含むすべての LXC ゲスト(ページ分割:offset/limit、デフォルト 500、最大 700) |
get_vm_config | ゲスト(QEMU のみ) | Proxmox ダイジェストを含む構成。description/cicustom/args の内容はベストエフォートで秘匿されます。認証情報のような形式のテキストは削除されますが、ここはシークレットを保存する安全な場所ではありません(sshkeys とネットワーク構成は保持されます) |
get_container_config | ゲスト(LXC のみ) | Proxmox ダイジェストを含む構成。description/cicustom/args の内容はベストエフォートで秘匿されます。認証情報のような形式のテキストは削除されますが、ここはシークレットを保存する安全な場所ではありません(sshkeys とネットワーク構成は保持されます) |
get_container_ip | ゲスト(LXC のみ) | ネットワークインターフェースとアドレス |
get_guest_status | ゲスト | 現在の実行時ステータス |
list_snapshots | ゲスト | 1つのゲストのスナップショット |
get_storage | ノード | 1つのノードから見えるストレージバックエンド |
list_backups | ストレージ | 1つのストレージバックエンド上のバックアップアーカイブ(ページ分割:offset/limit、デフォルト 500、最大 700) |
list_isos | ストレージ | 1つのストレージバックエンド上の ISO イメージ |
list_templates | ストレージ | 1つのストレージバックエンド上のコンテナテンプレート |
list_tasks | ノード。任意の typefilter、vmid、since、until、limit(1-500)、errors、statusfilter | 1つのノードの最近のタスク(ページ分割なし。Proxmox がサーバー側のウィンドウを適用し、limit を設定しない場合は直近 50 件) |
get_task_status | タスク | UPID による1つのタスクのステータス |
get_proxmox_change_set | 変更セット | 1つの変更セットの状態とプレビュー |
get_cluster_firewall_rules | クラスター | クラスター全体のファイアウォールルール |
get_cluster_firewall_options | クラスター | クラスター全体のファイアウォールオプション(有効化、デフォルトポリシー) |
list_firewall_security_groups | クラスター | クラスターに定義されたセキュリティグループ |
get_firewall_security_group_rules | クラスター+グループ | 1つのセキュリティグループに含まれるルール |
list_firewall_ipsets | クラスター | クラスター全体の IPSet |
get_firewall_ipset_entries | クラスター+IPSet | クラスター全体の1つの IPSet に含まれる CIDR エントリー |
list_firewall_aliases | クラスター | クラスター全体のファイアウォールアドレスエイリアス |
get_node_firewall_rules | ノード | 1つのノードのファイアウォールルール |
get_node_firewall_options | ノード | 1つのノードのファイアウォールオプション |
get_guest_firewall_rules | ゲスト | 1つのゲストのファイアウォールルール |
get_guest_firewall_options | ゲスト | 1つのゲストのファイアウォールオプション |
list_guest_firewall_aliases | ゲスト | 1つのゲストのファイアウォールアドレスエイリアス |
list_guest_firewall_ipsets | ゲスト | 1つのゲストに定義された IPSet |
get_guest_firewall_ipset_entries | ゲスト+IPSet | 1つのゲストの1つの IPSet に含まれる CIDR エントリー |
型固有の3つの読み取り機能は、存在しないエンドポイントを指定するのではなく、 名前によってもう一方のゲストタイプを拒否します。
ファイアウォールの読み取り機能は、Proxmox 自体が公開するスコープを反映しています。ノードレベルのファイアウォール
構成にはルールとオプションがありますが、エイリアス、IPSet、セキュリティグループはありません。
これらはクラスターとゲストのスコープにのみ存在します。ファイアウォールへの書き込みには
plan_firewall_change、approve_firewall_change、apply_firewall_change を使用します。
承認済みの変更セットがないファイアウォールへの書き込みは拒否されます。ラボモードと
二者承認には、ほかの管理対象の書き込みと同じルールが適用されます。
--lab-mode を指定すると、2人目のプリンシパルなしで保護対象ゲストのプランを承認できますが、
通常のゲスト、およびクラスターまたはノードのファイアウォールでは、引き続き1人の承認者が必要です。
ページネーション: get_vms、get_containers、list_backups にはクラスター/ノード/ストレージのサイズ上限がなく、大規模なデプロイでは MCP の結果に対する 512 KiB の上限を超える可能性があります。これらのツールは省略可能な offset と limit(デフォルト 500、最大 700。上限を十分に下回るよう設定)を受け取り、配列そのものではなく {items, total, offset, limit, has_more} を返すため、呼び出し元はリストが短いのか、次のページが必要なのかを判断できます。700 を超える limit は拒否され、暗黙に上限値へ丸められることはありません。各ページはスナップショットではありません。呼び出しごとに上流の全リストを再取得し、スライスする前に vmid(ゲスト)または volid(バックアップ)で並べ替えるため、レコードのページが移動するのは呼び出しの間に作成または削除された場合のみで、並べ替えによって移動することはありません。list_tasks は意図的にページネーションの対象外です。Proxmox の /nodes/{node}/tasks は、クライアントが把握できる合計件数のない独自のサーバー側ウィンドウを適用するため、その上にページネーションを重ねると、切り詰められたリストを完全なリストとして誤って報告してしまいます。
18 個の low ツール
ライフサイクル:start_vm、stop_vm*、shutdown_vm*、reset_vm*、start_container、stop_container*、restart_container*。
プロビジョニング:create_vm、create_container、clone_vm、download_iso、resize_disk、create_snapshot、create_backup、update_container_resources*。
タスクと変更セット:stop_task*、plan_proxmox_destroy、approve_proxmox_change_set。
* 実行中のゲストを中断します。この軸は階層とは別に追跡されます。ツールが low であってもサービスを停止させる可能性があり、保護ゲートは両方に適用されます。
注意が必要な点:
create_containerのデフォルトはunprivileged=1です。Proxmox は省略されたフィールドを特権付きとして解釈するため、何も指定しない場合に危険なオプションが選ばれてはいけません。- ハイパーバイザーに渡される設定キーは拒否されます:
hookscript、args、ホストマウントのmpN、パススルー用のhostpciN/usbN/devN/serialN/parallelN、生のlxc.*、および絶対ホストパスを含むすべての値。作成はlow操作であり、ノード上でコード実行につながってはいけません。 - 既存の VMID を指定した作成は拒否されます。Proxmox は同じエンドポイントへの POST によってバックアップを復元するため、このチェックがなければ、
lowの作成操作で稼働中のゲストを上書きできてしまいます。 resize_diskでできるのは拡張のみです。縮小はここでも Proxmox でもサポートされていません。qm resizeとpct resizeは縮小を拒否します。- コンテナーの停止は即時です。LXC にはグレースフルな方法がなく、
shutdown_vmは QEMU 専用です。
トークンのスコープを変更する
token set-scopes はトークンのデバイス、ツール、ゲスト、アクションのスコープをシークレットを再発行せずに変更するため、クライアントを再設定する必要はありません:
rust-proxmoxmcp token set-scopes --tokens-file <PATH> --name <NAME> \
[--devices <CSV|*>] [--tools <CSV|*>] \
[--guests <SELECTORS|*>] [--actions read,low,destructive] [--yes]
--devices/--tools を省略すると、そのスコープは変更されません。--guests と --actions は許可範囲をマージせず全面的に置き換えます。「置き換えるつもりだった」が「追加した」という意味に誤って解釈されてはならないゲスト許可範囲だからです。また、許可範囲には両方の要素が含まれるため、もう一方を推測すると誰にも指定されていない範囲へのアクセスを許可することになり、--actions だけの指定は拒否されます。
権限の拡大は権限昇格にあたるため、--yes が指定されていない場合は対話形式で確認されます。スコープを狭めても権限は付与されないため、確認は求められません。
--tools '*' では変更を伴うツールにアクセスできません。 WRITE_TOOLS は意図的にツールのワイルドカードから除外されているため、start_vm などのツールは明示的に指定する必要があります。指定されていない場合、事前チェックで 403 insufficient_scope が返されます。
認可モデル
ステージ 1:Bearer トークンとスコープ
ストリーミング HTTP の呼び出しには、すべて Bearer トークンが含まれます。トークンストア(tokens.json)では、トークンに次の項目が関連付けられています。
- ツールスコープ(
tools: ["*"]またはtools: ["get_nodes", "get_vms"]) - デバイススコープ(
devices: ["*"]またはdevices: ["pve3"]) - grant(ステージ 2 を参照)
ステージ 1 では、次の場合に拒否します。
- 無効または欠落している Bearer トークン(ループバックで
--allow-no-authが指定されている場合を除く) - トークンの
toolsリストに含まれていないツール - トークンの
devicesリストに含まれていないクラスター - ワイルドカードスコープでアクセスしようとした
WRITE_TOOLS内のツール。tools: ["*"]には意図的にそのレジストリが含まれないため、ワイルドカードトークンでは変更を伴うツールにアクセスできません。それぞれを明示的に指定する必要があります。
grant キーのないトークンは、ゲストを対象とするツールでは拒否されます。これはフェイルクローズの動作です。ゲストセレクターを宣言していないトークンが、ワイルドカードになってはなりません。
ステージ 2:ゲストの解決と保護
ゲストを対象とするツールは、VMID を GuestFacts レコード(名前、ノード、種類、タグ、プール)に解決し、トークンの grant を評価します。
{
"guests": ["vmid:600-699", "tag:disposable", "pool:test-vms"],
"actions": ["read"]
}
いずれかのセレクター項目に一致する場合、そのゲストはスコープ内です。次に、サーバーは保護の和集合を確認します。
- クラスターの
protected_vmidsに含まれるゲスト、またはprotected_tagsのタグ(デフォルト:["protected"])が付いているゲストは保護対象です。 - 保護対象のゲストは、
"guests": ["*"]が指定されていても、変更を伴うツールの対象にはできません。 - 読み取りツールでは、保護対象のゲストも通常どおり表示されます。
ゲストがスコープ外である場合、またはアクション階層(read/low/destructive)がトークンの grant に含まれていない場合、サーバーはゲストの詳細を漏らさず、「authorization failed」というエラーで拒否します。
設定
クラスターインベントリ:clusters.json
{
"version": 1,
"devices": {
"pve3": {
"endpoint": "https://pve3.example.org:8006",
"token_id": "mcp-automation@pve!mcp",
"token_secret_env": "PVE_PVE3_TOKEN",
"protected_vmids": [905, 906, 907],
"protected_tags": ["protected"]
}
},
"policy": {
"resource_cache_ttl_secs": 10
}
}
注: トップレベルのキーは clusters ではなく devices です。これは mecmcp-inventory の正式なエンベロープであり、サーバーは各エントリーをクラスターとして読み込みます。
このファイルに認証情報が含まれることはありません。 各クラスターは、次のいずれかの方法で API トークンシークレットを参照します。
token_secret_file(デフォルト):/etc/proxmoxmcp/secrets/<cluster>.tokenのような別ファイルを指定します。こちらがより安全な方法です。ファイルはclusters.jsonとtokens.jsonに使用するものと同じ強化されたローダーを通じて読み込まれます(0600、通常ファイル、サービスユーザー所有、O_NOFOLLOW)。また、認証情報がプロセス環境に入らないため、クラッシュダンプや/proc/<pid>/environに現れる可能性もありません。token_secret_env:環境変数の名前を指定します。systemd ユニットのEnvironmentFile=-/etc/proxmoxmcp/secrets.envでサポートされます(-プレフィックスにより、ファイルがなくてもエラーになりません)。認証情報がプロセス環境から読み取り可能になるため、環境変数を使う方法は安全性が劣ります。
どちらも mecmcp-secret を通じて OutboundSecret に読み込まれます。この値は破棄時にゼロ化され、Debug も Serialize も実装しません。
Proxmox 側のトークン:root@pam ではなく最小権限にする
token_id は Proxmox API トークンを指定します。このトークンの Proxmox 側の
権限は、このサーバーが制御できない第 2 の認可境界です。
上記のステージ 1 とステージ 2 では、MCP 呼び出し元が実行できる操作を制限しますが、リクエストが
クラスターに到達した後、基盤となる Proxmox 認証情報で何が許可されるかは制限しません。このサーバーに root@pam!... トークンを渡すと、
この第 2 の境界がなくなります。root@pam は Proxmox にハードコードされたスーパーユーザーであり、ACL
チェックを完全に回避します。詳しくは、
Proxmox VE 独自のユーザー管理ドキュメント
を参照してください。そのため、ロールもパスのスコープ制限もなく、clusters.json の設定でも
制限できません。このサーバーのバグ、トークンの窃取、または
tokens.json の過剰に広い grant があった場合、制限されたロールでフェイルクローズするのではなく、
クラスター全体の完全な制御を許すフェイルオープン状態になります。
代わりに pve レルムに専用の非 root ユーザーを作成し、このサーバーのツールが実際に使用する権限だけを持つカスタムロールを割り当ててください。
# A role scoped to exactly what rustproxmoxmcp's tools call, no more.
pveum role add ProxmoxMcp -privs "VM.Audit,Sys.Audit,Datastore.Audit,VM.PowerMgmt,VM.Snapshot,VM.Snapshot.Rollback,VM.Backup,VM.Clone,VM.Config.Disk,VM.Config.CPU,VM.Config.Memory,VM.Allocate,Datastore.AllocateSpace,Datastore.AllocateTemplate,Sys.AccessNetwork"
# A service account with no interactive password -- it is only ever reached
# through its API token.
pveum user add mcp-automation@pve --comment "rustproxmoxmcp service account"
# Grant the role cluster-wide (`/`), matching this server's own reach: guests
# migrate between nodes and clusters.json addresses a whole cluster, not one
# VM or pool. An operator who wants to scope one token to one pool of guests
# can grant ProxmoxMcp at `/pool/<name>` instead and mint a separate token per
# pool; that is a deployment choice this server does not require.
pveum acl modify / --users mcp-automation@pve --roles ProxmoxMcp
# --privsep 0: the token carries exactly the user's own permissions, so the
# role above is the token's complete privilege set with nothing left to grant
# or forget on a separate token-level ACL.
pveum user token add mcp-automation@pve mcp --privsep 0
最後のコマンドでは、トークンシークレットが一度だけ出力されます。これを、token_secret_file が指すファイル、または変数 token_secret_env が指定するファイルに保存してください。clusters.json 自体には絶対に記載しないでください。
ProxmoxMcp の各権限は、このサーバーが登録する特定のツールに対応しています。それ以外の権限は付与されません。Sys.PowerMgmt(ノードの再起動)、VM.Console または Sys.Console、VM.Migrate、Pool.*/Group.Allocate/Realm.Allocate/Permissions.Modify は付与されません。このサーバーは、これらの権限が必要な Proxmox エンドポイントを呼び出すことはありません。
| 権限 | 必要とするツール |
|---|---|
VM.Audit | すべてのゲストスコープの読み取り:get_vms、get_containers、get_vm_config、get_container_config、get_container_ip、get_guest_status、list_snapshots、get_guest_firewall_rules、get_guest_firewall_options、list_guest_firewall_aliases、list_guest_firewall_ipsets、get_guest_firewall_ipset_entries、およびすべてのプランと適用で実行されるゲストの名前解決/フィンガープリントの読み取り |
Sys.Audit | get_cluster_status、get_nodes、get_node_status、list_tasks、get_task_status、get_cluster_firewall_rules、get_cluster_firewall_options、list_firewall_security_groups、get_firewall_security_group_rules、list_firewall_ipsets、get_firewall_ipset_entries、list_firewall_aliases、get_node_firewall_rules、get_node_firewall_options |
Datastore.Audit | get_storage、list_backups、list_isos、list_templates |
VM.PowerMgmt | start_vm、stop_vm、shutdown_vm、reset_vm、start_container、stop_container、restart_container |
VM.Snapshot | create_snapshot、delete_snapshot(適用時の plan_proxmox_destroy 操作) |
VM.Snapshot.Rollback | rollback_snapshot(適用時の操作)。start: true の場合、Proxmox は VM.PowerMgmt を確認しないため、このサーバーでも start_vm/start_container のスコープと low ティアが必要です |
VM.Backup | create_backup、restore_backup(適用時の操作) |
VM.Clone | clone_vm |
VM.Config.Disk | resize_disk |
VM.Config.CPU、VM.Config.Memory | update_container_resources(コア数とメモリ/スワップ) |
VM.Config.Network | ゲストファイアウォールの適用(単一ゲストに対する apply_firewall_change)。Proxmox は /vms/{vmid} でこの権限を確認します |
VM.Allocate | create_vm、create_container、delete_vm/delete_container(適用時の destroy_guest 操作)、および既存の VMID を上書きする場合の restore_backup |
Datastore.AllocateSpace | create_vm/create_container(ディスク割り当て)、create_backup、delete_backup |
Datastore.AllocateTemplate | download_iso(保存先ストレージ) |
Sys.Modify | get_node_updates。/nodes/{node} に付与します。オプションの追加権限で、基本ロールには含まれません。以下を参照してください |
Datastore.Allocate | delete_iso。オプションの追加権限で、基本ロールには含まれません。以下を参照してください |
Sys.AccessNetwork | download_iso(download-url)。/nodes/{node} に付与します。API2/Storage/Status.pm は、ノードをスコープとするこの権限、または / に対する Sys.Audit+Sys.Modify のいずれかを受け入れます。ノードをスコープとする付与なら、ノードの再起動/ネットワーク/ディスク消去のアクセス権も同時に付与せずに済みます。download-url の Sys.AccessNetwork には PVE 8 以降が必要です。それより古いクラスターでは、代わりに / に対する、より広範な Sys.Audit+Sys.Modify の組み合わせを使用してください |
Datastore.Allocate は、上記のロールに意図的に含めていません。これを必要とするツールは delete_iso だけですが、Proxmox の API2/Storage/Content.pm の削除ハンドラーは、単一の ISO ボリュームではなく、ストレージに対してこの権限を確認します。「この ISO を削除する」権限だけを付与し、「このストレージの定義を変更または削除する」権限は付与しない、という Proxmox の権限はありません。基本ロールのように / で付与すると、トークンはクラスター内のすべてのストレージに対する管理者になります。ストレージ定義をクラスター全体から削除したり、別の場所に向け直したり、すべてのノードが接続する新しい NFS/CIFS/PBS マウントを追加したりできます。このデプロイで delete_iso が必要な場合は、クラスター全体ではなく、ISO を格納するストレージだけをスコープとして権限を付与してください。
# Optional: only if delete_iso must work. Scoped to one storage, not `/` —
# still lets the token edit or remove *that storage's* definition, but not
# any other storage in the cluster.
pveum role add ProxmoxMcpIsoDelete -privs "Datastore.Allocate"
pveum acl modify /storage/<iso-storage> --users mcp-automation@pve --roles ProxmoxMcpIsoDelete
Sys.Modify は get_node_updates にも必要です。Proxmox は
GET /nodes/{node}/apt/update を /nodes/{node} に対する Sys.Modify で制限しています。
これは基本ロールには含まれておらず、付与しない場合、get_node_updates は Proxmox の 403 を返します。このトークンで保留中のアップデートを確認する必要がある場合は、ノードにスコープを限定した別のロールを付与します。ただし、これによってトークンにそのノードのノード管理者権限(ネットワーク設定、ディスクの初期化と消去、apt など)が与えられるのであり、読み取り専用のビューではないことに注意してください。
# Optional: only if get_node_updates must work. Scoped to one node, not `/`.
pveum role add ProxmoxMcpNodeUpdates -privs "Sys.Modify"
pveum acl modify /nodes/<node> --users mcp-automation@pve --roles ProxmoxMcpNodeUpdates
/ に対する Sys.Modify は、上記のロールには意図的に含めていません。クラスタとノードのファイアウォール適用にはこれが必要です(クラスタオブジェクトは /、ノードのファイアウォールは /nodes/{node})。また、API2/Tasks.pm が呼び出し元の所有ではないタスクを停止する場合、stop_task にも必要です。このサーバー自身のタスクは常に自身のトークンに属するため、それらを停止するのに追加の権限は不要です。ファイアウォール変更の計画と承認では、読み取りのみが行われます(Sys.Audit または VM.Audit)。/ での Sys.Modify は広範なノード管理者権限(ノードのネットワーク設定、ディスクの初期化と消去、apt など)を付与するため、このトークンでクラスタまたはノードのファイアウォール変更を適用する必要がある場合、または 他の プリンシパルが開始したタスクを停止する必要がある場合に限り、追加してください。
pveum role modify ProxmoxMcp -privs "...,Sys.Modify" # append to the existing list
未検証です。本番環境で利用する前に、ラボの PVE で確認してください:
ネットワークデバイスを接続する create_vm、create_container、clone_vm の呼び出しには、PVE 8 以降でブリッジに対する SDN.Use も必要になる可能性があります。このロール一覧は SDN 管理下のブリッジで検証されていません。クラスタでそのようなブリッジを使用している場合は、まず使い捨てのゲストで計画/適用のサイクルをテストしてください。
plan_proxmox_destroy、approve_proxmox_change_set、get_proxmox_change_set が独自に行う Proxmox API 呼び出しは、ゲスト解決の読み取り(上記に記載した VM.Audit)のみです。承認は、このサーバー独自の変更セットストア内で行われる処理です。計画で必要となる Proxmox 権限は、上記の表でその op に対応するものです。その権限が消費されるのは、apply_proxmox_change_set が実際に実行したときだけです。
トークンストア:tokens.json
{
"version": 1,
"tokens": {
"demo-reader": {
"hash": "$argon2id$v=19$m=...",
"grant": {
"guests": ["vmid:600-699"],
"actions": ["read"]
},
"tools": ["*"],
"devices": ["pve3"]
}
}
}
rust-proxmoxmcp token add <name> でトークンを発行します。平文のトークンは一度だけ表示され、後から取得することはできません。変更セットを承認するトークンには、--actor-type human を渡してください。詳しくは変更管理を参照してください。
重要: grant キーのないトークンは、ゲストを指定するツールでは拒否されます。すべてのゲストへの読み取りアクセスを許可するには:
"grant": {
"guests": ["*"],
"actions": ["read"]
}
CLI フラグ
rust-proxmoxmcp は、mecmcp_runtime::cli::Cli のすべてのフラグ(トランスポート、バインド、TLS、許可されたホスト/オリジン、監査)を継承し、次の 1 つだけを追加します。
--clusters-file <path>— クラスタのインベントリ(デフォルト:/etc/proxmoxmcp/clusters.json)
streamable-HTTP では、--tokens-file または --allow-no-auth のいずれかが必要です。後者は、ループバックでの未認証の読み取りリクエストのみを許可します。書き込みツールは引き続き拒否されます。
すべてのフラグを確認するには、rust-proxmoxmcp --help を実行してください。
インストール
rust-proxmoxmcp の LXC を一からビルドする手順については、docs/HOW-TO-SETUP-LXC.md を参照してください。Docker については、docs/HOW-TO-SETUP-DOCKER.md を参照してください。
Docker で実行する
対話型の stdio サーバーでは、clusters.json、secrets/
ディレクトリ、書き込み可能な state/ ディレクトリを用意します。インベントリでは
標準の devices エンベロープを使用します。
{
"version": 1,
"devices": {
"pve-demo": {
"endpoint": "https://198.51.100.10:8006",
"token_id": "mcp-automation@pve!mcp",
"token_secret_file": "/etc/proxmoxmcp/secrets/pve-demo.token",
"protected_vmids": [100, 101],
"protected_tags": ["protected"]
}
},
"policy": {
"resource_cache_ttl_secs": 10
}
}
インベントリとシークレットファイルには、Proxmox のデプロイメント用に作成した値に置き換えるまで、プレースホルダーの認証情報が含まれます。通常ファイルであり、モードが 0600、所有者が UID/GID 65532:65532(イメージの実行時ユーザー)である必要があります。ホスト側の state ディレクトリを /var/lib/proxmoxmcp(イメージのボリューム)にマウントし、同じ UID でそのディレクトリに書き込めるようにします。このパスは変更セットの状態と監査キーの永続化ルートであり、server.json が公開するマウント先です。サブディレクトリではなく、ディレクトリ自体をマウントしてください。
chmod 0600 clusters.json secrets/pve-demo.token
chown -R 65532:65532 clusters.json secrets state
docker run --rm -i \
-v "$PWD/clusters.json:/etc/proxmoxmcp/clusters.json:ro" \
-v "$PWD/secrets:/etc/proxmoxmcp/secrets:ro" \
-v "$PWD/state:/var/lib/proxmoxmcp" \
ghcr.io/mechubsec/rustproxmoxmcp:latest \
--transport stdio \
--state-file /var/lib/proxmoxmcp/changeset-state.json
tokens.json(受信ベアラートークン)はストリーム可能な HTTP トランスポートでのみ使用されます。stdio にはトークンストアがないため、ここではマウントしません。--state-file を指定すると、再起動後も変更セットと操作の状態が保持されます。指定しない場合、コンテナの再起動時にすべての承認、プレビュー、実行中の適用処理が失われます。
このコマンドでは、イメージの ENTRYPOINT パスを維持し、HTTP の CMD を stdio に置き換えるため、この実行方法では受信 HTTP と TLS が無効になります。ストリーム可能な HTTP の二者承認モードとラボモードのセットアップについては、Docker の使い方ガイドを参照してください。
scripts/package-lxc.sh は Debian 13 LXC tarball をビルドします。packaging/lxc/install.sh は、その tarball 用の POSIX インストーラーです。インストーラーは次の処理を行います。
proxmoxmcpシステムユーザーを作成します- バイナリを
/usr/local/bin/rust-proxmoxmcpにインストールします - 例示用の設定ファイルを
/etc/proxmoxmcpにインストールします(モード0600、所有者proxmoxmcp)。ただし、ファイルが存在しない場合に限ります ProtectSystem=strictとReadWritePaths=/var/lib/proxmoxmcpを指定した、セキュリティ強化済みの systemd ユニットをインストールします- 次の手順と、アップグレード前にコンテナのスナップショットを作成するよう促す注意を表示します
アップグレードする前に: Proxmox でコンテナのスナップショットを作成してください。アップグレードに失敗した場合は、スナップショットにロールバックして元に戻せます。
開発メモ
クレート構成
rust-proxmoxmcp-core:ドメインロジック(インベントリ、解決、認可、カタログ)。サーバーやトランスポートは含みません。rust-proxmoxmcp:バイナリです。トランスポートを組み立て、インベントリを読み込み、カタログを提供します。
testing フィーチャー
コアクレートには、テストスイート用のモック HTTPS サーバーを構築するために rcgen、rustls、tokio-rustls、tempfile を取り込む、デフォルトでは無効の testing フィーチャーがあります。この仕組みはリリースバイナリにはコンパイルされません。
関連サーバー
| rustjunosmcp | rustpanosmcp | rustmistmcp | rustunifimcp | rustsdcmcp | rustproxmoxmcp | |
|---|---|---|---|---|---|---|
| ベンダー | Juniper Junos / SRX | Palo Alto PAN-OS | Juniper Mist | Ubiquiti UniFi Network | HPE Juniper Security Director Cloud | Proxmox VE |
| トランスポート | SSH 経由の NETCONF | HTTPS XML-API | HTTPS REST | HTTPS REST | HTTPS REST | HTTPS REST |
| ステータス | 提供中、v0.25.0 | 提供中、v0.14.0 | 基盤構築済み、読み取り専用の実テナント受け入れテストに合格 | 本番環境で稼働中 | リリース前(v0.1.0-lab) | 提供中、v0.11.0 |
6 つすべてが mecmcp を利用しています。これは、mechub のベンダーごとの MCP サーバーを支える共有 Rust クレートファミリーです。
イベントストアへの監査ログ転送
監査証跡はこのホストにとどまりません。このサーバーはファミリー標準(AUDIT-FORWARDING-STANDARD.md)に準拠しています。
生成したマシン上にしか存在しない監査記録は、監査証跡ではありません。記録の対象となる当事者が運用者であるマシン上のログファイルにすぎません。
出力(現在有効)
--audit-format json \
--audit-log-file /var/lib/proxmoxmcp/audit.jsonl
JSON は必須です。text 形式はターミナルで読むためのものであり、解析対象ではありません。このファイルは運用者向けの成果物であり、ローテーションする必要があります。サーバーがファイルを自ら切り詰めることはありませんが、mecmcp_audit::init_tracing が返すファイルハンドルを保持し、SIGHUP を受け取るとパスから再度開くため、ローテーターがファイルの名前を変更してプロセスにシグナルを送れば、ローテーションによる損失はありません。
すぐにインストールできる設定ファイルが
packaging/logrotate/rust-proxmoxmcp-audit に含まれています。
/var/lib/proxmoxmcp/audit.jsonl {
daily
rotate 14
missingok
notifempty
compress
delaycompress
su proxmoxmcp proxmoxmcp
postrotate
systemctl kill -s HUP rust-proxmoxmcp.service >/dev/null 2>&1 || true
endscript
}
copytruncate ではなく、名前変更して再度開きます。 SIGHUP を受け取ると、既存の clusters.json/tokens.json のホットリロードと同時に、パスから監査ファイルを再度開きます。そのため、postrotate でファイルの名前を変更してプロセスにシグナルを送ると、それ以降の書き込みはすべて同じパスに新しい inode として書き込まれます。名前変更前に書き込まれた内容は切り詰められず、名前変更後の内容も失われません。copytruncate はファイルをコピーしてからその場で切り詰めるため、この2つの処理の間に書き込まれた内容が失われます。
トランスポート(仕様策定済み、未実装)
SSDF の統合証拠契約に従い、レコードは SSDF の ssdf.audit にハッシュチェーン化された行として直接書き込まれます。これにより、行の削除や編集を検出できます。mecmcp#292 で追跡されています。
より低コストな syslog 経由の方式も設計されましたが、却下されました。機能はしますが、レコードがチェーン化されません。この仕組みのほかのリンクはすべて設計上改ざんを検出できるようになっています。計画ダイジェストは承認にひも付き、承認には個別の主体が記録され、token_verified_fields によって、裏付けられた来歴情報と自己申告情報が区別されます。最後の段階でチェーンが切れると、監査人がまさに必要とする箇所でその保証が失われます。この判断の理由は標準仕様に記録されています。
結果の読み方
token_verified_fields は、トークンが保証した来歴フィールドを示します。同じグループの残りのフィールド、つまり client_name、model_id、session_id はクライアントの自己申告情報であり、何によっても認証されていません。これらを同等のものとして扱わないでください。
request_id は、トランスポートイベント、ハンドラーイベント、および(Junos では)デバイスのコミットコメントを関連付けます。
ライセンス
運用とセキュリティ
エグレスフィルタリング
パッケージに含まれるユニットでは、エグレスを制御するために IPAddressDeny と IPAddressAllow を宣言しています。ただし、systemd は非特権 LXC 内ではこれらのディレクティブを適用できません。このフリートのゲストはすべて LXC です。systemd は cgroup BPF を使ってこれらを実装し、プログラムをロードできない場合はフェイルオープンするため、ユニットで完全なエグレスポリシーを宣言していても、何も適用されない可能性があります。systemd-analyze security は宣言を読み取りますが、この違いを判別できません。
インストーラーは実際に適用されているかを調べ、次の4つの判定のいずれかを表示します。
egress filter: ENFORCED— ホストが BPF プログラムをアタッチし、かつインストールされたユニットでポリシーが宣言されていますegress filter: NOT ENFORCED— ホストが BPF プログラムをアタッチできません。続いて対処方法が表示されますegress filter: NO POLICY— ホストは適用可能ですが、インストールされたユニットでIPAddressDenyが宣言されていません(カスタマイズ済みのユニットが保持され、パッケージ版を上書きしています。復元するには再インストールしてください)egress filter: UNKNOWN— プローブを実行できませんでした。何も保証されません
両方の条件が重要です。ホストの機能だけを確認すると、サービスが何もフィルタリングしていないのに成功と判定されます。
プローブでは、同じ BPF アタッチ機構を使う IP アカウンティングを使用するため、カウンターに値があればフィルターがアタッチされていることを確認できます。いつでも次のように確認できます。
systemctl show rust-proxmoxmcp.service -p IPEgressBytes --value
[no data] は、エグレスディレクティブが何も行っていないことを意味します。PROXMOXMCP_REQUIRE_EGRESS_FILTER=1 を設定すると、ENFORCED 以外の場合にインストーラーが処理を拒否します。これには UNKNOWN も含まれます。測定できないホストは、適用できないホストと同じく、保証がないためです。
systemd で制御できない場合
ENFORCED 以外の結果はすべて、ユニットのディレクティブが実証されていないことを意味します。制御は外側、つまりこのワークロードのパケットを実際に確認できるレイヤーに移してください。NOT ENFORCED と NO POLICY は、明らかに何も機能していないことを意味します。UNKNOWN は何も測定されておらず、機能している可能性も十分にあることを意味します。後者を前者と同じものとして扱わないでください。
ポリシーはランタイムによって変わりません(ただし、このユニットでは RFC 1918 から Proxmox API エンドポイントへのアクセスを許可しています)。
169.254.0.0/16とfd00:ec2::254を拒否します。クラウドメタデータへのアクセスを遮断します。侵害された HTTP クライアントから盗まれた認証情報に至る経路です。- リンクローカル(
fe80::/10)を拒否します。サポート対象のターゲットで使用されていません。 - DNS リゾルバーを除くローカルサブネットを拒否します。名前解決を維持しながらラテラルムーブメントを防ぎます(現在、このサーバーのユニットでは宣言されていません。必要に応じてドロップインで追加してください)。
使用する仕組みは異なります。ここにレシピを記載するのではなく、お使いのプラットフォームのドキュメントに従って設定してください。以下はレイヤーの一覧であり、手順ではありません。
| ランタイム | このワークロードのパケットを確認するレイヤー |
|---|---|
| Proxmox LXC / VM | ゲストごとのインターフェースファイアウォール |
| libvirt / KVM | ゲストインターフェース上の nwfilter |
| Kubernetes | 対応する CNI 上の NetworkPolicy の送信ルール |
| クラウドインスタンス | 両方のメタデータアドレスに対するゲスト内パケットフィルターと、それ以外すべてに対するセキュリティグループ |
| ベアメタル、または systemd が正常に動作する VM | ユニットのディレクティブ。このセクションは該当しません |
どの方法を選ぶ場合でも、よくある問題が 2 つあるため、次の点を確認してください。どちらも、制御が設定されているように見えて実際には機能しない原因になります。
- 送信ポリシーを受け入れても、それを適用しないレイヤーがあります。 コンテナのネットワーク接続や、一部の CNI 実装がよくある例です。
- クラウドメタデータは、クラウドファイアウォールを迂回することがよくあります。 EC2 では、IMDS トラフィックはセキュリティグループや NACL のレイヤーより下で処理されるため、そこで送信ルールを設定しても遮断されません。これは IPv6 エンドポイントにも当てはまります。
fd00:ec2::254はリンクローカルではなく ULA なので、「ファイアウォールが確認する通常のルーティングトラフィック」に分類してしまいやすいですが、実際にはそうではありません。制御はゲスト内で行うか、IMDS を完全に無効にする必要があります。プロバイダーの最新のメタデータ堅牢化ガイダンスを参照してください。内容は変更されるため、誤ると何も通知されません。
どの方法を選んでも、ワークロードの内部から実際に試していないルールは、単なる想定にすぎません。検証し、再起動後にも再検証してください。カーネル内のファイアウォールルールは、永続化しない限り永続しません。
MIT の下でライセンスされています。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。