使用ssh-keyscan解决Linux离线环境多节点互信任问题

在Linux系统管理中,多节点互信任是实现自动化运维、集群部署和分布式计算的基础。传统方法如ssh-copy-id依赖网络连接和交互式操作,在离线环境或需要批量部署的场景中效率低下。ssh-keyscan作为SSH协议的核心工具,通过非交互式收集主机公钥并生成known_hosts文件,为离线环境下的多节点互信任提供了高效解决方案。本文将详细解析其原理、实现步骤及常见问题处理。

一、传统互信任方法的局限性

1. ssh-copy-id的依赖条件

ssh-copy-id通过SSH协议将本地公钥复制到远程服务器的~/.ssh/authorized_keys文件中,实现免密登录。但其依赖以下条件:

  • 网络连通性:需能访问目标节点的SSH端口(默认22)。
  • 交互式操作:首次连接时需手动确认主机密钥(The authenticity of host...)。
  • 工具依赖:部分环境需安装expect等脚本工具处理交互。

在离线环境中,这些条件可能无法满足。例如,集群节点处于内网且无外网访问权限,或需批量配置数百个节点时,传统方法效率极低。

2. 自动化部署的痛点

在自动化部署场景中,如Kubernetes集群初始化、Hadoop集群搭建或CI/CD流水线,需预先配置节点间互信任。若依赖ssh-copy-id,则需为每个节点编写脚本处理交互,增加复杂性和出错概率。

二、ssh-keyscan的核心原理

1. 功能概述

ssh-keyscan是OpenSSH套件中的工具,用于收集远程主机的SSH公钥并生成known_hosts文件格式的输出。其特点包括:

  • 非阻塞I/O:并行扫描多个主机,大幅提升效率。
  • 无需登录:仅通过SSH协议握手获取公钥,不涉及认证流程。
  • 支持多种密钥类型:可指定RSA、ECDSA、Ed25519等算法。

2. 输出格式解析

执行ssh-keyscan example.com会返回类似以下内容:

example.com,192.168.1.1 ssh-rsa AAAAB3NzaC1yc2E...
example.com,192.168.1.1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY...
example.com,192.168.1.1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...

每行包含主机名/IP、密钥类型和Base64编码的公钥。此格式与~/.ssh/known_hosts文件兼容,可直接写入实现主机密钥缓存。

3. 散列化处理(HashKnownHosts)

为防止known_hosts文件泄露主机信息,OpenSSH支持对主机名/IP进行SHA-256散列化。通过-H选项,ssh-keyscan可生成散列化的条目:

|1|HASHVALUE|HOSTKEY...

散列化后,即使文件泄露,攻击者也无法直接获取主机名或IP。

三、离线环境下的实现步骤

1. 环境准备

假设需在3台离线节点(Node1、Node2、Node3)间建立互信任,步骤如下:

1.1 生成SSH密钥对

在所有节点上执行:

ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""

生成id_rsa(私钥)和id_rsa.pub(公钥)。

1.2 收集所有节点的公钥

在某一管理节点(如Node1)上,通过ssh-keyscan收集其他节点的公钥:

# 收集Node2的公钥
ssh-keyscan -t rsa,ecdsa,ed25519 Node2 > /tmp/Node2_keys
# 收集Node3的公钥
ssh-keyscan -t rsa,ecdsa,ed25519 Node3 > /tmp/Node3_keys

若节点IP已知,可直接使用IP:

ssh-keyscan -t rsa 192.168.1.2 > /tmp/Node2_keys
1.3 合并公钥并生成known_hosts

将所有节点的公钥合并到一个文件:

cat /tmp/Node2_keys /tmp/Node3_keys > /tmp/known_hosts_all
# 可选:散列化处理
ssh-keyscan -H Node2 Node3 >> /tmp/known_hosts_all_hashed

2. 分发公钥和known_hosts文件

2.1 分发公钥到authorized_keys

将各节点的公钥追加到其他节点的authorized_keys文件中。例如,在Node1上执行:

# 获取Node2的公钥
scp Node2:~/.ssh/id_rsa.pub /tmp/Node2_pub.key
# 将Node2的公钥追加到Node1的authorized_keys
cat /tmp/Node2_pub.key >> ~/.ssh/authorized_keys
# 重复上述步骤处理Node3

更高效的方式是使用循环:

NODES=("Node2" "Node3")
for node in "${NODES[@]}"; do
    scp ${node}:~/.ssh/id_rsa.pub /tmp/${node}_pub.key
    cat /tmp/${node}_pub.key >> ~/.ssh/authorized_keys
done
2.2 分发known_hosts文件

将合并后的known_hosts文件分发到所有节点:

# 在Node1上生成完整的known_hosts
ssh-keyscan -t rsa,ecdsa,ed25519 Node1 Node2 Node3 > /tmp/known_hosts_full
# 分发到Node2和Node3
scp /tmp/known_hosts_full Node2:~/.ssh/known_hosts
scp /tmp/known_hosts_full Node3:~/.ssh/known_hosts

3. 权限设置

确保.ssh目录和文件的权限正确:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/known_hosts

四、高级应用场景

1. 批量处理脚本

编写Shell脚本自动化上述流程:

#!/bin/bash
NODES=("Node1" "Node2" "Node3")
MANAGEMENT_NODE="Node1"

# 在管理节点上收集所有节点的公钥
for node in "${NODES[@]}"; do
    ssh-keyscan -t rsa,ecdsa,ed25519 ${node} >> /tmp/known_hosts_all
done

# 分发known_hosts和公钥
for node in "${NODES[@]}"; do
    if [ "${node}" != "${MANAGEMENT_NODE}" ]; then
        # 分发known_hosts
        scp /tmp/known_hosts_all ${node}:~/.ssh/known_hosts
        # 获取目标节点的公钥并追加到管理节点的authorized_keys
        scp ${node}:~/.ssh/id_rsa.pub /tmp/${node}_pub.key
        cat /tmp/${node}_pub.key >> ~/.ssh/authorized_keys
    fi
done

# 将管理节点的公钥分发到其他节点
for node in "${NODES[@]}"; do
    if [ "${node}" != "${MANAGEMENT_NODE}" ]; then
        scp ~/.ssh/id_rsa.pub ${node}:~/.ssh/management_pub.key
        ssh ${node} "cat ~/.ssh/management_pub.key >> ~/.ssh/authorized_keys"
    fi
done

2. 结合Ansible等工具

在Ansible中,可使用ssh-keyscan模块或直接调用命令:

- name: Collect SSH host keys
  command: ssh-keyscan -t rsa,ecdsa,ed25519 {{ item }}
  register: keyscan_output
  loop: "{{ groups['all'] }}"

- name: Create known_hosts file
  copy:
    content: "{{ keyscan_output.results | map(attribute='stdout') | join('\n') }}"
    dest: /tmp/known_hosts_all

- name: Distribute known_hosts
  copy:
    src: /tmp/known_hosts_all
    dest: ~/.ssh/known_hosts
    mode: 0644
  delegate_to: "{{ item }}"
  loop: "{{ groups['all'] }}"

五、常见问题与解决方案

1. 主机密钥变更错误

错误现象

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

原因:目标节点的SSH主机密钥被重置(如系统重装)。
解决方案

  • 删除known_hosts中对应的条目:
    ssh-keygen -R Node2
    
  • 或直接删除整个文件并重新生成:
    rm ~/.ssh/known_hosts
    ssh-keyscan Node2 Node3 > ~/.ssh/known_hosts
    

2. 权限问题

错误现象

Authentication refused: bad ownership or modes for directory /root/.ssh

原因.ssh目录或文件的权限不正确。
解决方案

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/known_hosts

3. 连接超时

错误现象

ssh: connect to host Node2 port 22: Connection refused

原因:目标节点未运行SSH服务或防火墙阻止连接。
解决方案

  • 确保SSH服务已启动:
    systemctl status sshd
    systemctl start sshd
    
  • 检查防火墙规则:
    iptables -L | grep 22
    iptables -I INPUT -p tcp --dport 22 -j ACCEPT
    

4. 密钥类型不匹配

错误现象

Unable to negotiate with Node2 port 22: no matching host key type found

原因:客户端和服务端支持的密钥类型不一致。
解决方案

  • ssh-keyscan中指定多种密钥类型:
    ssh-keyscan -t rsa,ecdsa,ed25519 Node2
    
  • 或在/etc/ssh/ssh_config中配置:
    HostKeyAlgorithms ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519
    

六、总结

ssh-keyscan通过非交互式收集主机公钥,为离线环境下的多节点互信任提供了高效解决方案。其核心优势包括:

  1. 无需网络交互:适用于内网或离线环境。
  2. 批量处理能力:可并行扫描数百个节点。
  3. 兼容性:输出格式与known_hosts文件完全兼容。
  4. 安全性:支持散列化处理,防止主机信息泄露。

在实际应用中,结合脚本或自动化工具(如Ansible)可进一步提升部署效率。通过合理配置权限和密钥类型,可确保互信任的稳定性和安全性。对于大规模集群,建议将ssh-keyscan集成到CI/CD流水线中,实现全自动化的节点管理。

Logo

欢迎加入我们的广州开发者社区,与优秀的开发者共同成长!

更多推荐