feat: 部署工程师 SSH 用机构独立密钥(每机构一把,防单点泄露)

deploy_test/deploy_prod 的 run_shell 执行 ssh/scp 时,之前默认走
~/.ssh/id_rsa(个人密钥),绕过 work_env 的机构密钥机制。现改为:
- capability_ctx 注入 org_id
- _inject_org_ssh_key:对 ssh/scp 命令自动注入 -i ~/.ssh/org_keys/{org_id}/id_ed25519
  (已显式 -i 不覆盖;非 ssh/scp 命令不注入;机构密钥不存在则回退默认)
- 仅部署角色(agent.deploy_test/deploy_prod)生效,不影响 develop 的 git ssh 等
This commit is contained in:
ymq 2026-08-24 07:20:28 +08:00
parent 99c1b60e31
commit df0bec8f7d

View File

@ -267,6 +267,50 @@ async def _run_shell(command, workdir, timeout=120):
return {"rc": -1, "stdout": "", "stderr": str(e)[:500]}
def _inject_org_ssh_key(command: str, org_id: str) -> str:
"""给 ssh/scp 命令注入机构独立密钥(每机构一把,防单点泄露)。
部署工程师deploy_test/deploy_prodSSH 到目标机时默认会走 ~/.ssh/id_rsa
个人密钥绕过机构密钥机制这里在 ssh/scp 命令前注入
`-i ~/.ssh/org_keys/{org_id}/id_ed25519`让部署用机构密钥连目标机
规则
- 命令含 ssh/scp 关键字才处理避免影响 git 等其他命令
- 已显式指定 -i 的不重复注入
- 机构密钥文件不存在则不注入回退默认密钥保持原行为
"""
if not command or not org_id:
return command
# 仅对 ssh/scp 命令注入(宽松匹配:命令以 ssh/scp 开头,或含 ' ssh ' 等)
stripped = command.lstrip()
if not (stripped.startswith("ssh ") or stripped.startswith("ssh\t") or
stripped.startswith("scp ") or stripped.startswith("scp\t")):
return command
if re.search(r'(^|\s)-i\s+\S+', command):
return command # 已显式指定密钥,不覆盖
from .work_env import ensure_org_key
try:
info = ensure_org_key(org_id)
key_path = info.get("key_path", "")
except Exception:
return command
if not key_path or not os.path.exists(key_path):
return command
# 在 ssh/scp 动词后注入 -i 密钥(保留原有前导空白)
return command[:len(command) - len(command.lstrip())] + \
_inject_prefix(stripped, f"-i {key_path} ")
def _inject_prefix(cmd: str, prefix: str) -> str:
"""在 ssh/scp 命令动词后插入 prefix密钥参数"""
parts = cmd.split(None, 1)
if not parts:
return cmd
verb = parts[0] # ssh 或 scp
rest = parts[1] if len(parts) > 1 else ""
return f"{verb} {prefix}{rest}"
async def _write_code_file(filepath, content):
"""写代码文件,自动创建父目录。"""
try:
@ -1067,6 +1111,12 @@ async def _exec_agent_tool(tool, params, workspace_dir, ctx=None):
elif tool == 'run_shell':
cmd = p.get('command', '')
if not cmd: return 'FAIL: 需要命令'
# 部署角色deploy_test/deploy_prodSSH 到目标机时,注入机构独立密钥
# (每机构一把,防单点泄露;否则默认走 ~/.ssh/id_rsa 个人密钥)。
_who = (ctx or {}).get('who', '') or ''
_org = (ctx or {}).get('org_id', '') or ''
if _who in ('agent.deploy_test', 'agent.deploy_prod') and _org:
cmd = _inject_org_ssh_key(cmd, _org)
r = await _run_shell(cmd, workspace_dir, timeout=120)
return f"rc={r['rc']}\nSTDOUT:\n{r['stdout'][:2000]}\nSTDERR:\n{r['stderr'][:1000]}"
elif tool == 'git_clone':
@ -1329,6 +1379,7 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
"who": role,
"agent_id": agent_id,
"task_id": task_id,
"org_id": org_id or '0',
}
system = (AGENT_SYSTEM_PROMPT