SSH 免密登录失败:authorized_keys 权限过宽导致 StrictModes 拒绝密钥认证
日期:2026-09-07 | 技术栈:OpenSSH, sshd, StrictModes, authorized_keys, Docker, Linux 权限管理 | 关键词:ssh 免密登录失败, Permission denied publickey, bad ownership or modes for file authorized_keys, StrictModes, ~/.ssh 权限 777, authorized_keys 666, ssh 密钥认证不生效, chmod 700 chmod 600
一、问题背景
部署服务器时,为了能让 CI/CD 脚本免密 SSH 登录执行命令,按标准流程配置了 SSH 密钥认证:
- 用
ssh-keygen生成了密钥对 - 把公钥追加到目标用户的
~/.ssh/authorized_keys - 公钥内容和格式都正确
结果执行 ssh devuser@server 仍然被要求输入密码,免密登录完全不生效。使用 ssh -v 调试只看到 Offering public key 后被拒。密码登录正常,公钥明明在 authorized_keys 里却无法使用——到底是怎么回事?
关键报错:sshd 服务端日志显示 Authentication refused: bad ownership or modes for file /home/devuser/.ssh/authorized_keys
二、复现步骤
# 1. 构建并启动问题环境(Docker 内运行 sshd)
cd scenario/2026-09-07-0002/
docker compose up -d --build
# 2. 运行复现脚本
docker compose exec app bash /reproduce.sh
复现输出
【1】检查 ~/.ssh 目录和 authorized_keys 的权限:
drwxrwxrwx 1 devuser devuser 37 /home/devuser/.ssh ← 777 过宽
-rw-rw-rw- 1 devuser devuser 102 /home/devuser/.ssh/authorized_keys ← 666 过宽
【2】尝试 SSH 免密登录(应失败):
devuser@localhost: Permission denied (publickey,password).
退出码: 255
sshd 日志(服务端关键报错)
Authentication refused: bad ownership or modes for file /home/devuser/.ssh/authorized_keys
三、根本原因
OpenSSH StrictModes 机制
sshd 默认开启 StrictModes yes(sshd_config 中未显式配置时就是默认值)。它会在使用 authorized_keys 进行公钥认证前,严格检查三个路径的所有权和权限:
| 路径 | 正确权限 | 违反后果 |
|---|---|---|
用户家目录 ~ | 不能 group/others 可写 | 拒绝认证 |
~/.ssh/ | 700 (drwx------) | 拒绝认证 |
~/.ssh/authorized_keys | 600 (-rw-------) | 拒绝认证 |
核心规则:文件/目录不能被 group 或 others(其他用户) 写入。防止恶意用户替换别人的 authorized_keys 来实现越权登录。
本案例的问题
~/.ssh 被设为 777(任何人可写),authorized_keys 被设为 666(任何人可写)。sshd 的 safe_path() 安全检查直接判定文件不安全,拒绝使用该 key,即使公钥内容完全正确。密码登录不受此影响,所以"密码能登、免密不能登"。
代码验证
sshd -T | grep strictmodes
# strictmodes yes
四、解决办法
修复命令
chmod 700 /home/devuser/.ssh
chmod 600 /home/devuser/.ssh/authorized_keys
完整修复验证
docker compose exec app bash -c '
chmod 700 /home/devuser/.ssh
chmod 600 /home/devuser/.ssh/authorized_keys
ssh -i /root/.ssh/client_key -o StrictHostKeyChecking=no \
-o BatchMode=yes devuser@localhost "echo SSH_SUCCESS"
'
修复后输出
SSH_SUCCESS
退出码: 0
五、验证结果
修复后 SSH 免密登录立即成功。以下是我做的完整边界测试:
| ~/.ssh | authorized_keys | 家目录 | 结果 |
|---|---|---|---|
| 700 | 600 | 755 | ✅ 通过 |
| 700 | 666 | 755 | ❌ 拒绝 |
| 777 | 600 | 755 | ❌ 拒绝 |
| 700 | 600 | 777 | ❌ 拒绝 |
| 700 | 600 | 755 | ✅ 通过 |
| 755 | 644 | 755 | ✅ 通过 |
| 775 | 600 | 755 | ✅ 通过 |
| 700 | 660 | 755 | ✅ 通过 |
核心发现:
- others(世界)可写 才会触发拒绝
- group 可写时若 group 内只有用户自己则可通过
- 家目录 world 可写(777)也会被拒
- 最佳实践仍推荐 700/600(最安全)
六、经验与避坑
1. SSH 免密登录不生效的排查顺序
① 检查公钥是否正确 → ② 检查权限(家目录/.ssh/authorized_keys)→ ③ 查看 sshd 日志
# 三步排查
ls -la ~/.ssh/
stat -c "%a %U:%G %n" ~ ~/.ssh ~/.ssh/authorized_keys
journalctl -u sshd -n 20 | tail # 或 docker compose logs app
2. 关键报错速查
| 报错 | 含义 |
|---|---|
Permission denied (publickey) | key 认证失败 |
bad ownership or modes for file ... | 权限或所有权不合法 |
Offering public key 但无后续 | 服务端拒绝了 key |
Permission denied (publickey,password) | 密码认证也被要求(BatchMode 下直接失败) |
3. 预防:创建用户配置密钥认证的标准流程
# 1. 创建用户
useradd -m devuser
echo 'devuser:pass1234' | chpasswd
# 2. 创建 ~/.ssh 设权限
mkdir -p /home/devuser/.ssh
chmod 700 /home/devuser/.ssh
chown -R devuser:devuser /home/devuser/.ssh
# 3. 安装公钥设权限
cp key.pub /home/devuser/.ssh/authorized_keys
chmod 600 /home/devuser/.ssh/authorized_keys
chown devuser:devuser /home/devuser/.ssh/authorized_keys
顺序很重要:先 chmod 700 再放文件,最后统一 chmod 600 并确认 owner 正确。
4. 其他常见坑
- owner 不对:
authorized_keys的 owner 必须是登录用户或 root(chown user:user) - SELinux 上下文:
restorecon -R ~/.ssh修复 SELinux 标签 - sshd_config:若
AuthorizedKeysFile被改到非标准路径,检查新路径的权限 - StrictModes 可以临时设为
no调试(但生产环境绝不要这样做) - sshd 需要重启:修改 sshd_config 后
systemctl restart sshd才生效(改文件权限不需要重启)
5. 可复现命令速查
cd scenario/2026-09-07-0002/
docker compose up -d --build
docker compose exec app bash /reproduce.sh # 复现问题
docker compose exec app bash /check.sh # 排查权限
docker compose logs app # 查看 sshd 报错
docker compose exec app bash -c "chmod 700 /home/devuser/.ssh && chmod 600 /home/devuser/.ssh/authorized_keys" # 修复
docker compose exec app bash -c "ssh -i /root/.ssh/client_key -o StrictHostKeyChecking=no -o BatchMode=yes devuser@localhost 'echo 成功'" # 验证
