环境 / Env
- agent-ssh-cli
0.4.1(npm 发行版 + Rust native)
- 远端 util-linux:su 通道建议
>= 2.38(su -P,见下);旧版需 fallback script 包装
动机 / Motivation
Agent 运维远端服务器时,身份切换 / 提权是刚需,但目标环境差异很大:
- 自管服务器:
sudo(认证用执行者自己的密码);
- 大公司 / 甲方:常无个人 sudo,只有共享业务账号(oracle / app / 中间件)密码或共享 root 密码,只能
su。
当前 exec 没有任何安全凭据通道。临时解法是远端落盘密码文件再重定向喂给 sudo -S——以下脚本来自本项目实际运维实践(由 AI 助手协助设计,已在真实服务器上运行验证),并非通用最佳实践,仅作现状说明:
agentsshcli exec <conn> "echo '<pw>' > /tmp/.pw && chmod 600 /tmp/.pw \
&& sudo -S -p '' <cmd> < /tmp/.pw; unlink /tmp/.pw"
该方案存在:① 明文密码进 argv(本机 shell history / ps / Agent 日志可见);② 远端临时文件落盘残留窗口;③ 复合 shell 串无法被 commandWhitelist 精确匹配(validate_command 对整条命令正则校验)。
建议:复用项目已有的 SSH 密码本地加密机制(password 首次连接被动加密 → secrets.json ChaCha20-Poly1305 密文 → passwordRef),新增 sudo / su 双提权通道,密码只经 SSH 通道 stdin 喂给远端,全程零 argv / 零落盘。
提案 / Proposal
config.json(双通道平铺,均可选)
用法
# sudo:密码取用优先级 sudoPasswordRef → SSH passwordRef(sudo 认证=自己密码的常见语义)→ 报错
agentsshcli exec --sudo conn "systemctl status app"
# su:切共享业务账号(suPassword 必配;suUser 缺省 root)
agentsshcli exec --su conn "ls -la ~/appdata"
凭据迁移规则
sudoPassword / suPassword 明文首次写入 → 首次对应 flag 连接时加密存入 secrets.json → 明文置空、写入 sudoPasswordRef / suPasswordRef;更新密码 = 重新填明文,下次连接自动覆盖(与现有 password 机制完全一致)。
验收标准 / Acceptance
实现方向 / Implementation notes
参考 native/src/main.rs(@ main):
- 加密基元可直接复用:
encrypt_password / decrypt_password(L770 / L789,ChaCha20-Poly1305,与密码语义无关)
- 迁移逻辑照抄
migrate_plain_password_for_connection(L847,泛型 serde_json::Value 操作字段);注意 ref key 命名空间隔离,避免 secrets.json HashMap insert 覆盖 SSH 密码密文
- 传输层:
execute_remote_command_with_session_async(L1841)当前只消费 stdout/stderr,从不写 channel stdin;需补一次 channel.data() + eof()(一次性喂密码,无需交互 pty 桥)
- 白名单兼容:
validate_command(L1504)校验用户原始命令,sudo/su 前缀为 CLI 注入的受信包装,不参与正则匹配
- 新字段遵循
RawConnection 的 #[serde(rename_all = "camelCase")],勿用小写
兼容与安全边界 / Notes
- 建议依赖 util-linux
su -P(>= 2.38,stdin 非 tty 时自动关 ECHO 防回显);旧版 fallback script -qec
- 不做
--sudo-password <明文> 之类的便捷参数(会把问题引回 argv)
- su 到 root 需要 root 密码,本特性仅提供安全通道,凭据托管责任在调用方
我可以尝试提交 PR
如果本特性方向认可,我可以尝试提交 PR:实现已拆分为可独立合入的提交(commit 1:凭据层 = 数据模型 + sudo/su 迁移 + 解密;commit 2:执行层 = 命令包装器 + channel stdin + 远端探测;commit 3:README 与测试),代码位置已按上文定位。也可以先只合入凭据层(commit 1),为后续能力打底。
参考 / workaround
上述远端落盘方案为当前临时做法,特性落地后退役。
环境 / Env
0.4.1(npm 发行版 + Rust native)>= 2.38(su -P,见下);旧版需 fallbackscript包装动机 / Motivation
Agent 运维远端服务器时,身份切换 / 提权是刚需,但目标环境差异很大:
sudo(认证用执行者自己的密码);su。当前
exec没有任何安全凭据通道。临时解法是远端落盘密码文件再重定向喂给sudo -S——以下脚本来自本项目实际运维实践(由 AI 助手协助设计,已在真实服务器上运行验证),并非通用最佳实践,仅作现状说明:该方案存在:① 明文密码进 argv(本机 shell history /
ps/ Agent 日志可见);② 远端临时文件落盘残留窗口;③ 复合 shell 串无法被commandWhitelist精确匹配(validate_command对整条命令正则校验)。建议:复用项目已有的 SSH 密码本地加密机制(
password首次连接被动加密 →secrets.jsonChaCha20-Poly1305 密文 →passwordRef),新增 sudo / su 双提权通道,密码只经 SSH 通道 stdin 喂给远端,全程零 argv / 零落盘。提案 / Proposal
config.json(双通道平铺,均可选)
{ "name": "conn-name", "host": "10.0.0.1", "username": "linxi", "password": "", "passwordRef": "agentsshcli:conn-name", // sudo 通道(可选;多数场景 sudoPassword 可留空,自动 fallback SSH 密码) "sudoUser": "root", "sudoPassword": "", "sudoPasswordRef": "agentsshcli:conn-name:sudo", // su 通道(可选;suUser + suPassword 需成对) "suUser": "oracle", "suPassword": "", "suPasswordRef": "agentsshcli:conn-name:su" }用法
凭据迁移规则
sudoPassword/suPassword明文首次写入 → 首次对应 flag 连接时加密存入secrets.json→ 明文置空、写入sudoPasswordRef/suPasswordRef;更新密码 = 重新填明文,下次连接自动覆盖(与现有password机制完全一致)。验收标准 / Acceptance
--sudo/--su时密码不出现在任何 argv / 本机与远端 shell history / 进程列表,远端零落盘sudoPasswordRef缺失时--sudo自动 fallback 复用passwordRef解密结果(SSH 密码认证场景免重复配置)data()+eof())喂入,命令包装:sudo →sudo -u <user> -S -p '' <cmd>;su →su -P -c '<cmd>' <user>su -P)环境自动 fallbackscript -qec包装--sudo/--su行为与现状完全一致(默认安全,零回归);两 flag 互斥;凭据缺失明确报错--json模式透传真实 exitCode / stdout / stderr,认证失败如实返回非零码secrets.json中 SSH / sudo / su 三组密文 key 互不覆盖(agentsshcli:<name>/:sudo/:su)实现方向 / Implementation notes
参考
native/src/main.rs(@ main):encrypt_password/decrypt_password(L770 / L789,ChaCha20-Poly1305,与密码语义无关)migrate_plain_password_for_connection(L847,泛型serde_json::Value操作字段);注意 ref key 命名空间隔离,避免secrets.jsonHashMap insert 覆盖 SSH 密码密文execute_remote_command_with_session_async(L1841)当前只消费 stdout/stderr,从不写 channel stdin;需补一次channel.data()+eof()(一次性喂密码,无需交互 pty 桥)validate_command(L1504)校验用户原始命令,sudo/su前缀为 CLI 注入的受信包装,不参与正则匹配RawConnection的#[serde(rename_all = "camelCase")],勿用小写兼容与安全边界 / Notes
su -P(>= 2.38,stdin 非 tty 时自动关 ECHO 防回显);旧版 fallbackscript -qec--sudo-password <明文>之类的便捷参数(会把问题引回 argv)我可以尝试提交 PR
如果本特性方向认可,我可以尝试提交 PR:实现已拆分为可独立合入的提交(commit 1:凭据层 = 数据模型 + sudo/su 迁移 + 解密;commit 2:执行层 = 命令包装器 + channel stdin + 远端探测;commit 3:README 与测试),代码位置已按上文定位。也可以先只合入凭据层(commit 1),为后续能力打底。
参考 / workaround
上述远端落盘方案为当前临时做法,特性落地后退役。