sshpass —— 密码登录自动化(仅限"首次推 key"场景)
一句话定义
sshpass 是个非常小的工具,作用只有一个:在脚本里把密码喂给 ssh,跳过那个交互式的密码提示。
OpenSSH 客户端本身故意不接受 -p <password> 这种参数(安全设计 —— 防止密码出现在 ps 输出和 shell history 里)。sshpass 是个绕过这个限制的小hack。
典型场景
90% 的合理使用就一个:拿到一批全新机器,第一次推公钥时,没法用 key 登(因为 key 还没推过去),只能用密码。这时用 sshpass 在脚本里自动化推 key,推完立刻关密码登录。
Day0 §2.1:
PUBKEY=$(cat ~/.ssh/id_rsa.pub)
for ip in 10.0.24.28 10.0.24.29 10.0.24.30 10.0.24.31 10.0.24.32; do
sshpass -p "$IP_PWD" ssh -o StrictHostKeyChecking=accept-new root@$ip "
mkdir -p /root/.ssh && chmod 700 /root/.ssh
grep -qxF '$PUBKEY' /root/.ssh/authorized_keys 2>/dev/null \
|| echo '$PUBKEY' >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
"
done
绝对不应该用 sshpass 的场景:
- 日常运维(key + ssh-agent 完全够)
- 生产部署(必须用 key 或更严的认证)
- 把密码写在 git 仓库的脚本里(密码泄漏 = 机器沦陷)
安装
# Ubuntu / Debian
apt-get install -y sshpass
# macOS(Homebrew 默认不收,因为它"不安全")
brew install hudochenkov/sshpass/sshpass
# CentOS / RHEL(需要 EPEL)
yum install -y epel-release
yum install -y sshpass
brew 官方不收的原因:维护者觉得 sshpass 鼓励了不好的做法(把密码写脚本里)。要装得走第三方 tap,或者别用 macOS 跑这种脚本(拉个 Linux VM)。
三种"喂密码"方式
按安全性排序,越往后越安全。
1. -p <password> —— 命令行参数(最不安全)
sshpass -p 'MyPassw0rd' ssh root@host
问题:
ps aux | grep sshpass会看到密码(任何能登录这台机器的人都能 sniff)- 写进 shell history(
.bash_history、.zsh_history) - 容易被 git commit 进代码库
只用于一次性、纯本机、马上要删 history 的场景。
2. -f <file> —— 从文件读
echo 'MyPassw0rd' > /tmp/pw
chmod 600 /tmp/pw
sshpass -f /tmp/pw ssh root@host
rm /tmp/pw
比 -p 好:不出现在 ps 输出。但密码以明文落盘,依然不理想。
3. -e —— 从环境变量 SSHPASS 读(推荐)
export SSHPASS='MyPassw0rd'
sshpass -e ssh root@host
unset SSHPASS
# 或者一次性,密码只在子进程里
SSHPASS='MyPassw0rd' sshpass -e ssh root@host
环境变量不出现在 ps、不写文件。配合 read -s 输入更安全:
read -s -p "Password: " SSHPASS
export SSHPASS
for ip in ...; do
sshpass -e ssh root@$ip 'echo OK'
done
unset SSHPASS
read -s 不回显密码到屏幕。Day0 推 key 脚本应该这么写。
完整推 key 工作流(建议这么干)
#!/bin/bash
set -euo pipefail
read -s -p "IDC machine password: " SSHPASS
export SSHPASS
echo
PUBKEY=$(cat ~/.ssh/id_ed25519.pub)
IPS=(10.0.24.28 10.0.24.29 10.0.24.30 10.0.24.31 10.0.24.32)
for ip in "${IPS[@]}"; do
echo "=== $ip ==="
sshpass -e ssh \
-o StrictHostKeyChecking=accept-new \
-o ConnectTimeout=10 \
root@"$ip" "
mkdir -p /root/.ssh && chmod 700 /root/.ssh
grep -qxF '$PUBKEY' /root/.ssh/authorized_keys 2>/dev/null \
|| echo '$PUBKEY' >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
" && echo " ✅ key pushed" || echo " ❌ failed"
done
unset SSHPASS
# 验证:每台机器用 key 都能登
for ip in "${IPS[@]}"; do
ssh -o BatchMode=yes -o ConnectTimeout=5 root@"$ip" 'echo OK' \
&& echo "$ip: ✅ key works" \
|| echo "$ip: ❌ key broken"
done
关键点:
read -s输入密码不回显set -euo pipefail让脚本一遇到错就退出(避免某台失败但继续推后面的)StrictHostKeyChecking=accept-new:首次自动信任并写known_hostsConnectTimeout=10:默认 TCP 重试好几分钟,加超时让脚本快速失败grep -qxF ... ||实现幂等(详见 ssh-keygen.md)- 推完立刻验证 key 工作(
BatchMode=yes禁交互),确认 OK 之后再关密码
常见踩坑
坑 1:推 key 成功了,但马上 PasswordAuthentication no,结果 key 不工作
顺序不能反。正确顺序:
- 推 key
- 用
ssh -o BatchMode=yes验证 key 能登 - 确认验证通过后,再去关 PasswordAuthentication(见 sshd.md)
如果 key 没工作就关了密码 → 你既不能用密码登、也不能用 key 登 → 锁外面。
坑 2:密码里有特殊字符($, !, `)
sshpass -p 'My$ecret!' ssh root@host # 单引号是必须的
sshpass -p "My$ecret!" ssh root@host # ❌ $ecret 被 shell 当变量展开(多半为空)
含 $ 或 ` 的密码:用单引号。
坑 3:sshpass 报 "command not found"
-bash: sshpass: command not found
很多发行版默认不装。apt-get install -y sshpass。
坑 4:sshpass 报 "Permission denied",密码明明对
可能是远端禁用了 keyboard-interactive、或者只允许公钥。先用普通 ssh root@host 手动试一次,看远端到底允许什么认证方式:
ssh -v -o PreferredAuthentications=password root@host
# Authentications that can continue: publickey
# 说明远端禁了密码
这种机器你根本不需要 sshpass —— 它已经强制 key 了。
坑 5:first connection 的 host key 提示卡住
sshpass -e ssh root@host # ❌ 卡在 "Are you sure you want to continue?"
sshpass 喂的是登录密码,不是这个提示的"yes"回答。加 StrictHostKeyChecking=accept-new 让 ssh 自动信任首次:
sshpass -e ssh -o StrictHostKeyChecking=accept-new root@host
坑 6:CI 环境里把密码 commit 进了 .env 或脚本
经典事故。CI 跑得好好的,过了一年某个新员工把 repo 设成 public 或者 fork 出去 → 密码泄漏。
防御:
- 用 CI 平台的 secret 机制(GitHub Actions secrets、GitLab CI variables)
- 任何脚本里都用
${SSHPASS}读环境变量,不在文件里写明文 - 不要 commit
.env:.gitignore加.env、*.password、secrets/*
更彻底的:根本不要用 sshpass 进 CI。CI 部署用 SSH key(CI 提供的 deploy key 机制),key 不会泄漏的同时还能精确撤销。
替代方案
Ansible(首选)
ansible all -i hosts -m authorized_key \
-a "user=root state=present key='$(cat ~/.ssh/id_ed25519.pub)'" \
-k # -k 提示输入密码
-k 一次性输密码,Ansible 帮你处理所有节点。无需 sshpass。
ssh-copy-id
for ip in ...; do
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@$ip
done
每台机器交互式问一次密码。机器多就累,机器少(≤5)足够。
直接走 cloud-init / IDC console 注入 key
很多 IDC 创建机器时可以选"注入 SSH 公钥"。这是最佳做法 —— 机器开出来就有 key,根本不需要密码登录这一步。
关联命令
- ssh —— sshpass 只是 ssh 前面的一个 wrapper
- ssh-keygen —— 先生成 key 才有可推的公钥
- sshd —— 推完 key 之后关密码登录的服务端配置
ansible/ssh-copy-id—— 生产环境的更好替代