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:
parent
99c1b60e31
commit
df0bec8f7d
@ -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_prod)SSH 到目标机时,默认会走 ~/.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_prod)SSH 到目标机时,注入机构独立密钥
|
||||
# (每机构一把,防单点泄露;否则默认走 ~/.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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user