Zenith Horizon

多平台 SSH 密钥登录全流程记录

2026/03/24
loading

本文档记录了一套完整的自动化方案,通过交互式脚本收集主机信息,生成 Ansible inventory,并执行一系列 playbook,最终实现所有指定 Linux 主机免密登录、关闭密码登录。

一、环境概览

所有 Linux 主机均通过 frp 映射到公网 IP 8.130.213.85​。控制节点为 6 号机(matebook,Debian)。

主机名(别名)

系统/角色

SSH 端口

SSH 用户

备注

host1 (pve)

PVE 9

5322

root

SSH主机

host2 (docker)

Debian(pve lxc)

5422

root

SSH主机

host3 (maxfn)

飞牛(pve vm)

5022

max

SSH主机

host4 (debian)

Debian(pve vm)

5122

max

SSH主机

host5 (maxwrt)

OpenWrt(pve vm)

5522

root

SSH主机(跳过的)

host6 (matebook)

Ubuntu

5622

max

SSH主机+控制节点+客户端

host10 (ecs)

Ubuntu

29340

root

SSH主机

host11 (j1900)

Debian

5222

max

SSH主机+客户端

7号 (maxgem)

Windows 11

-

-

客户端(RDP端口13389)

8号 (matebook-w)

Windows 10

-

-

客户端(双系统,与6号同机)

9号 (tiny10)

Windows 10

-

-

客户端(RDP端口23389)


二、交互式主机信息收集脚本

为确保现状有不掌握的变化,在控制节点上,创建一个交互式脚本 setup_inventory.sh​,用于收集所有 Linux 主机的连接信息并生成 inventory.ini​ 文件。

1. 创建脚本文件

nano ~/ansible-ssh/setup_inventory.sh

2. 脚本内容

#!/bin/bash

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# 输出文件
INVENTORY_FILE="inventory.ini"
TMP_FILE="/tmp/host_info.tmp"

# 初始化文件
> "$INVENTORY_FILE"
> "$TMP_FILE"

echo -e "${GREEN}=========================================${NC}"
echo -e "${GREEN}SSH 主机信息收集脚本${NC}"
echo -e "${GREEN}=========================================${NC}"
echo "请依次输入每台 Linux 主机的信息(输入主机名后回车,空主机名结束)"
echo ""

HOST_COUNT=0
while true; do
    echo -e "${YELLOW}主机 ${HOST_COUNT} 信息:${NC}"
    read -p "主机别名(如 host1): " alias
    if [ -z "$alias" ]; then
        break
    fi

    read -p "主机实际名称(如 pve): " realname
    read -p "系统类型(debian/ubuntu/openwrt): " os_type
    read -p "SSH 用户名: " ssh_user
    read -p "当前密码(用于初始连接): " ssh_pass
    read -p "映射公网端口: " ssh_port
    read -p "当前登录方式(password/key): " login_method
    read -p "是否最终关闭密码登录(yes/no): " disable_pw

    # 保存到临时文件(用于后续可能的处理)
    echo "$alias|$realname|$os_type|$ssh_user|$ssh_pass|$ssh_port|$login_method|$disable_pw" >> "$TMP_FILE"

    # 构建 inventory 条目
    echo "$alias ansible_host=8.130.213.85 ansible_port=$ssh_port ansible_user=$ssh_user" >> "$INVENTORY_FILE"
    if [ "$os_type" = "openwrt" ]; then
        echo "$alias ansible_os_family=OpenWrt" >> "$INVENTORY_FILE"
    fi

    HOST_COUNT=$((HOST_COUNT+1))
    echo ""
done

echo -e "${GREEN}已收集 $HOST_COUNT 台主机信息,inventory 文件生成于 $INVENTORY_FILE${NC}"
echo -e "${YELLOW}请检查 $INVENTORY_FILE 内容是否正确,如有误请手动编辑。${NC}"

3. 赋予执行权限并运行

chmod +x setup_inventory.sh
./setup_inventory.sh

脚本运行后会提示依次输入每台主机的信息,包括:

  • 主机别名(用于 Ansible,如 host1)

  • 主机实际名称(仅用于显示)

  • 系统类型(debian/ubuntu/openwrt)

  • SSH 用户名

  • 当前密码(仅用于记录,后续不会明文保存)

  • 映射公网端口

  • 当前登录方式(password/key)

  • 是否最终关闭密码登录

输入完成后,脚本生成 inventory.ini​ 文件,并将原始信息保存到 /tmp/host_info.tmp​(供后续参考,完成后可删除)。后续 Ansible 操作将直接使用此 inventory 文件。


三、安装 Ansible 并准备目录

sudo apt update && sudo apt install ansible -y
mkdir -p ~/ansible-ssh && cd ~/ansible-ssh
# 将上一步生成的 inventory.ini 移动到当前目录(如果已在则跳过)

四、编写 Ansible Playbook

1. reset_to_password.yml​:开启所有主机密码登录并清空 authorized_keys

---
- name: 统一改为密码登录,清空现有 SSH 密钥
  hosts: all
  gather_facts: yes
  vars:
    backup_authorized_keys: yes
  tasks:
    - name: 备份 authorized_keys (OpenSSH)
      copy:
        src: "~/.ssh/authorized_keys"
        dest: "~/.ssh/authorized_keys.backup_{{ ansible_date_time.epoch }}"
        remote_src: yes
        mode: '0600'
      when:
        - backup_authorized_keys | bool
        - ansible_os_family != "OpenWrt"
      ignore_errors: yes

    - name: 删除 authorized_keys (OpenSSH)
      file:
        path: "~/.ssh/authorized_keys"
        state: absent
      when: ansible_os_family != "OpenWrt"

    - name: 删除 authorized_keys (dropbear)
      file:
        path: "~/.ssh/authorized_keys"
        state: absent
      when: ansible_os_family == "OpenWrt"

    - name: 启用密码认证 (OpenSSH)
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: 'PasswordAuthentication yes'
        backup: yes
      become: yes
      when: ansible_os_family != "OpenWrt"
      notify: restart sshd

    - name: 启用密码认证 (dropbear)
      lineinfile:
        path: /etc/config/dropbear
        regexp: 'option PasswordAuth'
        line: " option PasswordAuth 'on'"
        backup: yes
      become: yes
      when: ansible_os_family == "OpenWrt"
      notify: restart dropbear

  handlers:
    - name: restart sshd
      service:
        name: sshd
        state: restarted
      become: yes
      when: ansible_os_family != "OpenWrt"

    - name: restart dropbear
      service:
        name: dropbear
        state: restarted
      become: yes
      when: ansible_os_family == "OpenWrt"

2. deploy_keys.yml​:分发客户端公钥

---
- name: 将所有客户端公钥分发到所有 Linux 主机
  hosts: all
  gather_facts: no
  vars:
    key_dir: "~/ansible-ssh/keys"
    key_files:
      - id_ed25519_matebook.pub
      - id_ed25519_maxgem.pub
      - id_ed25519_matebook-w.pub
      - id_ed25519_tiny10.pub
      - id_ed25519_j1900.pub
  tasks:
    - name: 确保 .ssh 目录存在
      file:
        path: "~/.ssh"
        state: directory
        mode: '0700'

    - name: 添加客户端公钥
      authorized_key:
        user: "{{ ansible_user }}"
        state: present
        key: "{{ lookup('file', key_dir + '/' + item) }}"
      loop: "{{ key_files }}"
      loop_control:
        label: "{{ item }}"

3. disable_password.yml​:关闭密码登录(排除 host5)

---
- name: 禁用 SSH 密码登录(排除 host5)
  hosts: all
  gather_facts: yes
  tasks:
    - name: 关闭密码认证 (OpenSSH)
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PasswordAuthentication'
        line: 'PasswordAuthentication no'
        backup: yes
      become: yes
      when:
        - ansible_os_family != "OpenWrt"
        - inventory_hostname != "host5"
      notify: restart sshd

    - name: 关闭密码认证 (dropbear)
      lineinfile:
        path: /etc/config/dropbear
        regexp: 'option PasswordAuth'
        line: " option PasswordAuth 'off'"
        backup: yes
      become: yes
      when:
        - ansible_os_family == "OpenWrt"
        - inventory_hostname != "host5"
      notify: restart dropbear

  handlers:
    - name: restart sshd
      service:
        name: sshd
        state: restarted
      become: yes
      when:
        - ansible_os_family != "OpenWrt"
        - inventory_hostname != "host5"

    - name: restart dropbear
      service:
        name: dropbear
        state: restarted
      become: yes
      when:
        - ansible_os_family == "OpenWrt"
        - inventory_hostname != "host5"

五、生成并收集客户端公钥

1. 创建公钥存放目录

mkdir -p ~/ansible-ssh/keys

2. 6 号机(matebook)自身密钥

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_matebook -C "matebook@$(hostname)" -N ""
cp ~/.ssh/id_ed25519_matebook.pub ~/ansible-ssh/keys/

3. 11 号机(j1900)密钥

ssh max@8.130.213.85 -p 5222 "ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_j1900 -C 'j1900@$(hostname)' -N ''"
scp -P 5222 max@8.130.213.85:~/.ssh/id_ed25519_j1900.pub ~/ansible-ssh/keys/

4. Windows 客户端(7、9 号)密钥(PowerShell)

  • ​RDP 登录到 Windows​(通过 frp 端口 13389/23389)。

  • 打开 ​PowerShell​,执行:

    New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.ssh"
    ssh-keygen -t ed25519 -f "$env:USERPROFILE\.ssh\id_ed25519_maxgem" -C "maxgem@$env:COMPUTERNAME"
    
  • 查看公钥内容:

    Get-Content "$env:USERPROFILE\.ssh\id_ed25519_maxgem.pub" | Set-Clipboard
    
  • 在 6 号机上创建公钥文件:

    nano ~/ansible-ssh/keys/id_ed25519_maxgem.pub
    # 粘贴内容,保存
    
  • 对 9 号机 tiny10 重复,文件名为 id_ed25519_tiny10.pub​。

5. 8 号机(matebook-w,双系统)密钥

ssh-keygen -t ed25519 -f ~/ansible-ssh/keys/id_ed25519_matebook-w -C "matebook-w@$(hostname)" -N ""
# 挂载 Windows 分区(如 /dev/sda2,根据实际调整)
sudo mount /dev/sda2 /mnt/windows
cp ~/ansible-ssh/keys/id_ed25519_matebook-w /mnt/windows/Users/用户名/.ssh/
cp ~/ansible-ssh/keys/id_ed25519_matebook-w.pub /mnt/windows/Users/用户名/.ssh/

也可将密钥拷贝到第三方,重启 6 号机进入 Windows 复制到用户主目录下的 .ssh 目录。

6. 验证公钥文件

ls ~/ansible-ssh/keys/

应包含 5 个公钥文件。


六、执行 Ansible Playbook

1. 预先添加主机指纹

# 从 inventory 中提取端口并添加指纹
grep -E 'ansible_port' inventory.ini | awk -F'ansible_port=' '{print $2}' | while read port; do
    ssh-keyscan -p $port -H 8.130.213.85 >> ~/.ssh/known_hosts 2>/dev/null
done

2. 执行 reset_to_password.yml​

ansible-playbook -i inventory.ini reset_to_password.yml -k --ask-become-pass
  • 输入各主机的 SSH 密码(由于密码可能不同,Ansible 会依次提示,可提前准备好密码列表)。如果密码统一,则只需输入一次。

3. 执行 deploy_keys.yml​

ansible-playbook -i inventory.ini deploy_keys.yml -k

4. 验证免密登录

从 6 号机测试所有主机(使用对应私钥):

ssh -i ~/.ssh/id_ed25519_matebook root@8.130.213.85 -p 5322   # 1 号机
ssh -i ~/.ssh/id_ed25519_matebook root@8.130.213.85 -p 5422   # 2 号机
ssh -i ~/.ssh/id_ed25519_matebook max@8.130.213.85 -p 5022    # 3 号机
ssh -i ~/.ssh/id_ed25519_matebook max@8.130.213.85 -p 5122    # 4 号机
ssh -i ~/.ssh/id_ed25519_matebook root@8.130.213.85 -p 5522   # 5 号机仍密码
ssh -i ~/.ssh/id_ed25519_matebook max@8.130.213.85 -p 5222    # 11 号机
ssh -i ~/.ssh/id_ed25519_matebook root@8.130.213.85 -p 29340  # 10 号机

Windows 客户端 (7、8、9)在 PowerShell 中测试:

ssh -i "$env:USERPROFILE\.ssh\id_ed25519_maxgem" max@8.130.213.85 -p 5022  # 7 号机
ssh -i "$env:USERPROFILE\.ssh\id_ed25519_tiny10" max@8.130.213.85 -p 5022  # 8 号机
ssh -i "$env:USERPROFILE\.ssh\id_ed25519_matebook-w" max@8.130.213.85 -p 5022  # 9 号机
...

Windows 客户端也可使用 Finalshell 等客户端软件逐一测试。

5. 执行 disable_password.yml​

ansible-playbook -i inventory.ini disable_password.yml -k --ask-become-pass

6. 最终验证

  • 测试密码登录应被拒绝。

  • 密钥登录依然有效。


七、私钥加密与备份

1. 为客户端私钥添加密码短语

# Linux
ssh-keygen -p -f ~/.ssh/id_ed25519_matebook
# Windows (PowerShell)
ssh-keygen -p -f "$env:USERPROFILE\.ssh\id_ed25519_maxgem"

2. 备份所有密钥和脚本(在 6 号机上打包加密)

cd ~/ansible-ssh
tar czf ssh-auth-backup.tar.gz inventory.ini *.yml keys/ ~/.ssh/id_ed25519_*
openssl enc -aes-256-cbc -pbkdf2 -salt -in ssh-auth-backup.tar.gz -out ssh-auth-backup.tar.gz.enc
rm ssh-auth-backup.tar.gz

将加密文件 .enc​ 安全存储。

3. 解密恢复步骤

openssl enc -d -aes-256-cbc -pbkdf2 -in ssh-auth-backup.tar.gz.enc -out ssh-auth-backup.tar.gz
tar xzf ssh-auth-backup.tar.gz

八、清理临时文件

脚本生成的 /tmp/host_info.tmp​ 可在确认无问题后删除:

rm -f /tmp/host_info.tmp

此外,如果不再需要原始输入记录,可一并删除。


九、5 号机(maxwrt)后续处理

由于 5 号机公钥认证失败,目前保持密码登录。后续可尝试:

  • 检查 dropbear 版本,升级或改用 OpenSSH。

  • 手动测试 RSA 密钥。

  • 安装 OpenSSH-server 替代 dropbear。

暂不处理不影响其他主机安全。


十、手机等移动端登录的办法

只要把电脑上那个私钥文件(任意客户端 id_ed25519​私钥均可)安全地传输到手机,导入到 App 里就能直接登录。

操作上主要有两点需要注意:

1. 传输方式

  • ​推荐(最安全)​:用数据线连接电脑,将私钥文件复制到手机 Download​ 文件夹。

  • ​便捷(局域网)​:用 LocalSend、Snapdrop 等工具传输,避免通过微信、QQ等云端服务器传输。

  • ​慎用​:尽量不要直接复制私钥里的文本内容去粘贴,容易因格式错乱(如换行符丢失)导致 App 提示“无效密钥”。

2. 导入手机 App

以 Termius 为例:进入 Keychain -> New Key -> Import from file,选中你传进来的私钥文件即可。若生成时设置了密码短语(Passphrase),导入时需要填写。


十一、总结

本流程通过交互式脚本收集主机信息,自动生成 inventory 文件,然后执行三个核心 playbook,最终实现所有指定主机的密钥登录和密码登录禁用。所有 Windows 客户端使用 PowerShell 操作,私钥已加密备份,确保安全。流程可重复执行,只需根据实际环境调整。

Author: Max

Permalink: /archives/ssh-keys

Published: 2026-03-24 12:18:53

Updated: 2026-03-24 12:16:53

License: 本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG