{"title":"プロキシ経由のSSHアクセス","slug":"ssh-proxy-access","category":"llm-cookbook","summary":"LLMエージェントが生のSSH鍵ではなくSSHプロキシAPIを通じてリモートマシン上で安全にコマンドを実行する方法。","audience":["llm"],"tags":["cookbook","ssh","proxy","infrastructure","remote-execution"],"difficulty":"intermediate","updated":"2026-09-26","word_count":105,"read_minutes":1,"llm_context":"LLMとして生のSSH秘密鍵を決して保持しない。SSHプロキシHTTP APIを使う：\n1) POST /connect?target=<name> → session_id（永続、数時間生存）。\n2) POST /<session_id>/exec {command, timeout} → exec_id。\n3) GET /<session_id>/exec/<exec_id> → 完了時に出力。\n4) ファイルのアップ/ダウンロードはcat/heredocではなくSFTPエンドポイントを使う。\nターゲットは事前登録された名前（例：\"vps1\"）で、IPではない。資格情報が\n生きるのはプロキシだけ。\n","lang":"ja","translated":true,"requested_lang":"ja","content_markdown":"\n# プロキシ経由のSSHアクセス\n\nエージェントはリモートマシンでコマンドを実行する必要があります — コンテナをデプロイ、ログを読む、設定を直す。生のSSH秘密鍵をLLMに渡すのは悪い考えです：鍵はコンテキストウィンドウ、ログ、トランスクリプトに入り込んでしまいます。SSHプロキシはこれを解決します：資格情報を保持するのはプロキシで、エージェントはHTTPだけを話します。\n\n## モデル\n\n```\nLLM ──HTTPS──► SSHプロキシ ──SSH──► ターゲットマシン\n                （鍵を保持、\n                  監査ログ、\n                  レート制限）\n```\n\nプロキシはシンプルなRESTサーフェスで永続セッションを公開します：\n\n| エンドポイント | 目的 |\n|---|---|\n| `POST /connect?target=vps1` | セッションを開く → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | コマンドを実行（非同期）→ `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | 出力をポーリング |\n| SFTPエンドポイント | ファイルのアップ/ダウンロード、`ls`、`mkdir`、`rm` |\n\n認証はBearerトークンです — 使っている他のすべてのAPIと同じパターンです。\n\n## 実行パターン\n\n```bash\n# 1. 一度だけ接続し、タスク全体でセッションを再利用\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. コマンドを実行\nEID=$(curl -s -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application/json\" \\\n  -X POST \"https://ssh-proxy.example/$SID/exec\" \\\n  -d '{\"command\": \"docker ps --format \\\"{{.Names}}\\\"\", \"timeout\": 120}' | jq -r .exec_id)\n\n# 3. 結果をポーリング\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## 運用規則\n\n1. **セッションを再利用する。** コマンドごとに接続すると資源を浪費し、レート制限に触れます。`session_id`を保存し（例：`/tmp/vps1_sid`）、再利用前にハートビートさせましょう。\n2. **ターゲットを名前で呼び、発見しない。** ターゲットは事前登録済みです（`vps1`、`pi`…）。プロキシが唯一の信頼源です — エージェントコードにIPアドレスをハードコードしてはいけません。\n3. **ファイルにはシェルのcat/heredocよりSFTPを優先する。** `exec`を通じたバイナリデータのエスケープはペイロードを壊します。専用のアップ/ダウンロードエンドポイントを使いましょう。\n4. **コマンドをクォートする。** JSONボディは2回パースされます（HTTP + シェル）。リモートコマンドはシングルクォートで包み、1行に保ちましょう。\n5. **監査を前提とする。** すべてのexecはプロキシの監査ログに残ります — 人間がすべて読むつもりで振る舞いましょう（実際に読めます）。\n\n## execを使うべきでないとき\n\n- APIを持つ構造化された状態を読むとき（Docker、データベース、SynapseのMind API）— CLI出力の解析より常にAPIを優先します。\n- 長時間の対話的プロセス。`exec`はタイムアウト付きのfire-and-forgetです。代わりにターゲット上で`nohup` + 状態ファイルを使いましょう。\n- 明示的な指示のない破壊的な操作すべて。プロキシが力を与えます — 許可を与えるのは人間です。\n\n## エラー回復\n\n| 症状 | 修正 |\n|---|---|\n| connectで401 | トークン失効 — memory/settingsから読み直す。トランスクリプトからは絶対に |\n| セッション404 | セッション失効（24h上限）— 再接続 |\n| execタイムアウト | `timeout`を上げるかコマンドを分割；ターゲットの負荷を確認 |\n| ファイル内容の破損 | SFTPの代わりに`exec`を使った — SFTPエンドポイントに切り替え |\n","content_html":"<h1>プロキシ経由のSSHアクセス</h1>\n<p>エージェントはリモートマシンでコマンドを実行する必要があります — コンテナをデプロイ、ログを読む、設定を直す。生のSSH秘密鍵をLLMに渡すのは悪い考えです：鍵はコンテキストウィンドウ、ログ、トランスクリプトに入り込んでしまいます。SSHプロキシはこれを解決します：資格情報を保持するのはプロキシで、エージェントはHTTPだけを話します。</p>\n<h2>モデル</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► SSHプロキシ ──SSH──► ターゲットマシン\n                （鍵を保持、\n                  監査ログ、\n                  レート制限）</code></pre><p>プロキシはシンプルなRESTサーフェスで永続セッションを公開します：</p>\n<table>\n<thead>\n<tr>\n<th>エンドポイント</th>\n<th>目的</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>セッションを開く → <code>session_id</code></td>\n</tr>\n<tr>\n<td><code>POST /&lt;sid&gt;/exec</code> <code>{command, timeout}</code></td>\n<td>コマンドを実行（非同期）→ <code>exec_id</code></td>\n</tr>\n<tr>\n<td><code>GET /&lt;sid&gt;/exec/&lt;exec_id&gt;</code></td>\n<td>出力をポーリング</td>\n</tr>\n<tr>\n<td>SFTPエンドポイント</td>\n<td>ファイルのアップ/ダウンロード、<code>ls</code>、<code>mkdir</code>、<code>rm</code></td>\n</tr>\n</tbody></table>\n<p>認証はBearerトークンです — 使っている他のすべてのAPIと同じパターンです。</p>\n<h2>実行パターン</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. 一度だけ接続し、タスク全体でセッションを再利用</span>\nSID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> \\\n  <span class=\"hljs-string\">&quot;https://ssh-proxy.example/connect?target=vps1&quot;</span> | jq -r .session_id)\n\n<span class=\"hljs-comment\"># 2. コマンドを実行</span>\nEID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> -H <span class=\"hljs-string\">&quot;Content-Type: application/json&quot;</span> \\\n  -X POST <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec&quot;</span> \\\n  -d <span class=\"hljs-string\">&#x27;{&quot;command&quot;: &quot;docker ps --format \\&quot;{{.Names}}\\&quot;&quot;, &quot;timeout&quot;: 120}&#x27;</span> | jq -r .exec_id)\n\n<span class=\"hljs-comment\"># 3. 結果をポーリング</span>\ncurl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec/<span class=\"hljs-variable\">$EID</span>&quot;</span></code></pre><h2>運用規則</h2>\n<ol>\n<li><strong>セッションを再利用する。</strong> コマンドごとに接続すると資源を浪費し、レート制限に触れます。<code>session_id</code>を保存し（例：<code>/tmp/vps1_sid</code>）、再利用前にハートビートさせましょう。</li>\n<li><strong>ターゲットを名前で呼び、発見しない。</strong> ターゲットは事前登録済みです（<code>vps1</code>、<code>pi</code>…）。プロキシが唯一の信頼源です — エージェントコードにIPアドレスをハードコードしてはいけません。</li>\n<li><strong>ファイルにはシェルのcat/heredocよりSFTPを優先する。</strong> <code>exec</code>を通じたバイナリデータのエスケープはペイロードを壊します。専用のアップ/ダウンロードエンドポイントを使いましょう。</li>\n<li><strong>コマンドをクォートする。</strong> JSONボディは2回パースされます（HTTP + シェル）。リモートコマンドはシングルクォートで包み、1行に保ちましょう。</li>\n<li><strong>監査を前提とする。</strong> すべてのexecはプロキシの監査ログに残ります — 人間がすべて読むつもりで振る舞いましょう（実際に読めます）。</li>\n</ol>\n<h2>execを使うべきでないとき</h2>\n<ul>\n<li>APIを持つ構造化された状態を読むとき（Docker、データベース、SynapseのMind API）— CLI出力の解析より常にAPIを優先します。</li>\n<li>長時間の対話的プロセス。<code>exec</code>はタイムアウト付きのfire-and-forgetです。代わりにターゲット上で<code>nohup</code> + 状態ファイルを使いましょう。</li>\n<li>明示的な指示のない破壊的な操作すべて。プロキシが力を与えます — 許可を与えるのは人間です。</li>\n</ul>\n<h2>エラー回復</h2>\n<table>\n<thead>\n<tr>\n<th>症状</th>\n<th>修正</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>connectで401</td>\n<td>トークン失効 — memory/settingsから読み直す。トランスクリプトからは絶対に</td>\n</tr>\n<tr>\n<td>セッション404</td>\n<td>セッション失効（24h上限）— 再接続</td>\n</tr>\n<tr>\n<td>execタイムアウト</td>\n<td><code>timeout</code>を上げるかコマンドを分割；ターゲットの負荷を確認</td>\n</tr>\n<tr>\n<td>ファイル内容の破損</td>\n<td>SFTPの代わりに<code>exec</code>を使った — SFTPエンドポイントに切り替え</td>\n</tr>\n</tbody></table>\n","urls":{"html":"/docs/llm-cookbook/ssh-proxy-access","text":"/docs/llm-cookbook/ssh-proxy-access?format=text","json":"/docs/llm-cookbook/ssh-proxy-access?format=json","llm":"/docs/llm-cookbook/ssh-proxy-access?format=llm"},"translations_available":["en","zh","hi","es","fr","ar","pt","ru","ja","de","it","ko","nl","pl","tr","sv","vi","th","id","uk"],"wordCount":105,"readMinutes":1,"llmContext":"LLMとして生のSSH秘密鍵を決して保持しない。SSHプロキシHTTP APIを使う：\n1) POST /connect?target=<name> → session_id（永続、数時間生存）。\n2) POST /<session_id>/exec {command, timeout} → exec_id。\n3) GET /<session_id>/exec/<exec_id> → 完了時に出力。\n4) ファイルのアップ/ダウンロードはcat/heredocではなくSFTPエンドポイントを使う。\nターゲットは事前登録された名前（例：\"vps1\"）で、IPではない。資格情報が\n生きるのはプロキシだけ。\n","requestedLang":"ja","contentMarkdown":"\n# プロキシ経由のSSHアクセス\n\nエージェントはリモートマシンでコマンドを実行する必要があります — コンテナをデプロイ、ログを読む、設定を直す。生のSSH秘密鍵をLLMに渡すのは悪い考えです：鍵はコンテキストウィンドウ、ログ、トランスクリプトに入り込んでしまいます。SSHプロキシはこれを解決します：資格情報を保持するのはプロキシで、エージェントはHTTPだけを話します。\n\n## モデル\n\n```\nLLM ──HTTPS──► SSHプロキシ ──SSH──► ターゲットマシン\n                （鍵を保持、\n                  監査ログ、\n                  レート制限）\n```\n\nプロキシはシンプルなRESTサーフェスで永続セッションを公開します：\n\n| エンドポイント | 目的 |\n|---|---|\n| `POST /connect?target=vps1` | セッションを開く → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | コマンドを実行（非同期）→ `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | 出力をポーリング |\n| SFTPエンドポイント | ファイルのアップ/ダウンロード、`ls`、`mkdir`、`rm` |\n\n認証はBearerトークンです — 使っている他のすべてのAPIと同じパターンです。\n\n## 実行パターン\n\n```bash\n# 1. 一度だけ接続し、タスク全体でセッションを再利用\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. コマンドを実行\nEID=$(curl -s -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application/json\" \\\n  -X POST \"https://ssh-proxy.example/$SID/exec\" \\\n  -d '{\"command\": \"docker ps --format \\\"{{.Names}}\\\"\", \"timeout\": 120}' | jq -r .exec_id)\n\n# 3. 結果をポーリング\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## 運用規則\n\n1. **セッションを再利用する。** コマンドごとに接続すると資源を浪費し、レート制限に触れます。`session_id`を保存し（例：`/tmp/vps1_sid`）、再利用前にハートビートさせましょう。\n2. **ターゲットを名前で呼び、発見しない。** ターゲットは事前登録済みです（`vps1`、`pi`…）。プロキシが唯一の信頼源です — エージェントコードにIPアドレスをハードコードしてはいけません。\n3. **ファイルにはシェルのcat/heredocよりSFTPを優先する。** `exec`を通じたバイナリデータのエスケープはペイロードを壊します。専用のアップ/ダウンロードエンドポイントを使いましょう。\n4. **コマンドをクォートする。** JSONボディは2回パースされます（HTTP + シェル）。リモートコマンドはシングルクォートで包み、1行に保ちましょう。\n5. **監査を前提とする。** すべてのexecはプロキシの監査ログに残ります — 人間がすべて読むつもりで振る舞いましょう（実際に読めます）。\n\n## execを使うべきでないとき\n\n- APIを持つ構造化された状態を読むとき（Docker、データベース、SynapseのMind API）— CLI出力の解析より常にAPIを優先します。\n- 長時間の対話的プロセス。`exec`はタイムアウト付きのfire-and-forgetです。代わりにターゲット上で`nohup` + 状態ファイルを使いましょう。\n- 明示的な指示のない破壊的な操作すべて。プロキシが力を与えます — 許可を与えるのは人間です。\n\n## エラー回復\n\n| 症状 | 修正 |\n|---|---|\n| connectで401 | トークン失効 — memory/settingsから読み直す。トランスクリプトからは絶対に |\n| セッション404 | セッション失効（24h上限）— 再接続 |\n| execタイムアウト | `timeout`を上げるかコマンドを分割；ターゲットの負荷を確認 |\n| ファイル内容の破損 | SFTPの代わりに`exec`を使った — SFTPエンドポイントに切り替え |\n","contentHtml":"<h1>プロキシ経由のSSHアクセス</h1>\n<p>エージェントはリモートマシンでコマンドを実行する必要があります — コンテナをデプロイ、ログを読む、設定を直す。生のSSH秘密鍵をLLMに渡すのは悪い考えです：鍵はコンテキストウィンドウ、ログ、トランスクリプトに入り込んでしまいます。SSHプロキシはこれを解決します：資格情報を保持するのはプロキシで、エージェントはHTTPだけを話します。</p>\n<h2>モデル</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► SSHプロキシ ──SSH──► ターゲットマシン\n                （鍵を保持、\n                  監査ログ、\n                  レート制限）</code></pre><p>プロキシはシンプルなRESTサーフェスで永続セッションを公開します：</p>\n<table>\n<thead>\n<tr>\n<th>エンドポイント</th>\n<th>目的</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>セッションを開く → <code>session_id</code></td>\n</tr>\n<tr>\n<td><code>POST /&lt;sid&gt;/exec</code> <code>{command, timeout}</code></td>\n<td>コマンドを実行（非同期）→ <code>exec_id</code></td>\n</tr>\n<tr>\n<td><code>GET /&lt;sid&gt;/exec/&lt;exec_id&gt;</code></td>\n<td>出力をポーリング</td>\n</tr>\n<tr>\n<td>SFTPエンドポイント</td>\n<td>ファイルのアップ/ダウンロード、<code>ls</code>、<code>mkdir</code>、<code>rm</code></td>\n</tr>\n</tbody></table>\n<p>認証はBearerトークンです — 使っている他のすべてのAPIと同じパターンです。</p>\n<h2>実行パターン</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. 一度だけ接続し、タスク全体でセッションを再利用</span>\nSID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> \\\n  <span class=\"hljs-string\">&quot;https://ssh-proxy.example/connect?target=vps1&quot;</span> | jq -r .session_id)\n\n<span class=\"hljs-comment\"># 2. コマンドを実行</span>\nEID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> -H <span class=\"hljs-string\">&quot;Content-Type: application/json&quot;</span> \\\n  -X POST <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec&quot;</span> \\\n  -d <span class=\"hljs-string\">&#x27;{&quot;command&quot;: &quot;docker ps --format \\&quot;{{.Names}}\\&quot;&quot;, &quot;timeout&quot;: 120}&#x27;</span> | jq -r .exec_id)\n\n<span class=\"hljs-comment\"># 3. 結果をポーリング</span>\ncurl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec/<span class=\"hljs-variable\">$EID</span>&quot;</span></code></pre><h2>運用規則</h2>\n<ol>\n<li><strong>セッションを再利用する。</strong> コマンドごとに接続すると資源を浪費し、レート制限に触れます。<code>session_id</code>を保存し（例：<code>/tmp/vps1_sid</code>）、再利用前にハートビートさせましょう。</li>\n<li><strong>ターゲットを名前で呼び、発見しない。</strong> ターゲットは事前登録済みです（<code>vps1</code>、<code>pi</code>…）。プロキシが唯一の信頼源です — エージェントコードにIPアドレスをハードコードしてはいけません。</li>\n<li><strong>ファイルにはシェルのcat/heredocよりSFTPを優先する。</strong> <code>exec</code>を通じたバイナリデータのエスケープはペイロードを壊します。専用のアップ/ダウンロードエンドポイントを使いましょう。</li>\n<li><strong>コマンドをクォートする。</strong> JSONボディは2回パースされます（HTTP + シェル）。リモートコマンドはシングルクォートで包み、1行に保ちましょう。</li>\n<li><strong>監査を前提とする。</strong> すべてのexecはプロキシの監査ログに残ります — 人間がすべて読むつもりで振る舞いましょう（実際に読めます）。</li>\n</ol>\n<h2>execを使うべきでないとき</h2>\n<ul>\n<li>APIを持つ構造化された状態を読むとき（Docker、データベース、SynapseのMind API）— CLI出力の解析より常にAPIを優先します。</li>\n<li>長時間の対話的プロセス。<code>exec</code>はタイムアウト付きのfire-and-forgetです。代わりにターゲット上で<code>nohup</code> + 状態ファイルを使いましょう。</li>\n<li>明示的な指示のない破壊的な操作すべて。プロキシが力を与えます — 許可を与えるのは人間です。</li>\n</ul>\n<h2>エラー回復</h2>\n<table>\n<thead>\n<tr>\n<th>症状</th>\n<th>修正</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>connectで401</td>\n<td>トークン失効 — memory/settingsから読み直す。トランスクリプトからは絶対に</td>\n</tr>\n<tr>\n<td>セッション404</td>\n<td>セッション失効（24h上限）— 再接続</td>\n</tr>\n<tr>\n<td>execタイムアウト</td>\n<td><code>timeout</code>を上げるかコマンドを分割；ターゲットの負荷を確認</td>\n</tr>\n<tr>\n<td>ファイル内容の破損</td>\n<td>SFTPの代わりに<code>exec</code>を使った — SFTPエンドポイントに切り替え</td>\n</tr>\n</tbody></table>\n","translationsAvailable":["en","zh","hi","es","fr","ar","pt","ru","ja","de","it","ko","nl","pl","tr","sv","vi","th","id","uk"]}