随着 OpenSSH 的不断更新和安全性的提升,最新版本的 OpenSSH 服务器(如 openssh-server-9.8p1-1
)逐步减少了对某些旧公钥算法的支持,转而仅支持更安全的算法如 ssh-ed25519
。这在增强安全性的同时,也给一些依赖旧公钥算法的工具和脚本带来了兼容性问题。本文将探讨如何解决 Python 脚本在这种环境下无法正常连接服务器的问题。
问题背景
在最新版本的 OpenSSH 服务器中,默认配置仅支持 ssh-ed25519
公钥算法。这导致了许多工具和脚本,尤其是基于 Python 的 Paramiko 库无法正常工作,因为它们默认使用的公钥算法可能不包括 ssh-ed25519
。以下是一个常见的错误信息:
Exception (client): Unable to agree on a pubkey algorithm for signing a 'ssh-rsa' key!
解决方案
要解决这个问题,我们需要显式地指定 Paramiko 使用 ssh-ed25519
算法。以下是详细步骤和代码示例。
1. 更新 Paramiko 和加密库
首先,确保 Paramiko 和其依赖库(如 Cryptography)是最新的。使用以下命令进行更新:
代码语言:javascript复制
bash
pip install --upgrade paramiko cryptography
2. 创建和配置 SSH 客户端
在 Python 脚本中,使用 Paramiko 创建 SSH 客户端,并配置其使用 ssh-ed25519
公钥算法进行连接。以下是完整的代码示例:
python
import paramiko
def create_ssh_client(server, port, user, password):
"""创建SSH客户端并连接到服务器"""
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 配置公钥算法
ssh.connect(server, port=port, username=user, password=password, allow_agent=False, look_for_keys=False)
# 获取 transport 并设置公钥算法
transport = ssh.get_transport()
transport.get_security_options().key_types = ['ssh-ed25519']
return ssh
# 使用示例
server = '10.0.0.16'
port = 22
user = 'root'
password = ' ~qCw?Ao2i08mqTp'
ssh_client = create_ssh_client(server, port, user, password)
# 示例:上传文件
sftp = ssh_client.open_sftp()
sftp.put('upload_file.py', '/tmp/upload_file.py')
sftp.close()
ssh_client.close()
3. 验证和调试连接
为了确保连接成功,可以启用调试模式查看详细的连接过程:
代码语言:javascript复制
bash
ssh -vvv root@10.0.0.16
这将输出详细的调试信息,有助于识别和解决潜在的问题。
4. 工具开发
我们可以扩展该脚本以支持命令行参数输入主机信息、区分执行命令和上传文件的功能。下面是一个完整的解决方案,包括处理命令行参数的代码,并区分执行命令和上传文件的逻辑。
完整代码:
代码语言:javascript复制
python
import paramiko
import argparse
import sys
def create_ssh_client(server, port, user, password):
"""创建SSH客户端并连接到服务器"""
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 配置公钥算法
ssh.connect(server, port=port, username=user, password=password, allow_agent=False, look_for_keys=False)
# 获取 transport 并设置公钥算法
transport = ssh.get_transport()
transport.get_security_options().key_types = ['ssh-ed25519']
return ssh
def upload_file(ssh_client, local_path, remote_path):
"""上传文件到远程服务器"""
sftp = ssh_client.open_sftp()
sftp.put(local_path, remote_path)
sftp.close()
def execute_command(ssh_client, command):
"""在远程服务器上执行命令"""
stdin, stdout, stderr = ssh_client.exec_command(command)
print(stdout.read().decode())
print(stderr.read().decode())
def main():
parser = argparse.ArgumentParser(description='SSH 文件上传和命令执行工具')
parser.add_argument('server', help='服务器地址')
parser.add_argument('port', type=int, help='服务器端口')
parser.add_argument('user', help='用户名')
parser.add_argument('password', help='密码')
parser.add_argument('--upload', nargs=2, metavar=('local_path', 'remote_path'), help='上传文件')
parser.add_argument('--command', help='执行远程命令')
args = parser.parse_args()
try:
ssh_client = create_ssh_client(args.server, args.port, args.user, args.password)
if args.upload:
local_path, remote_path = args.upload
upload_file(ssh_client, local_path, remote_path)
elif args.command:
execute_command(ssh_client, args.command)
else:
print("请提供 --upload 或 --command 参数以执行相应的操作。")
ssh_client.close()
except Exception as e:
print(f"连接或操作过程中发生错误: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
使用说明
上传文件:
代码语言:javascript复制
bash
python script.py <server> <port> <user> <password> --upload <local_path> <remote_path>
例如:
代码语言:javascript复制
bash
python script.py 10.0.0.16 22 root ' ~qCw?Ao2i08mqTp' --upload upload_file.py /tmp/upload_file.py
执行命令:
代码语言:javascript复制
bash
python script.py <server> <port> <user> <password> --command "<command>"
例如:
代码语言:javascript复制
bash
python script.py 10.0.0.16 22 root ' ~qCw?Ao2i08mqTp' --command "ls -l /tmp"
该脚本可以灵活地根据命令行参数来处理不同的操作需求,包括上传文件和执行命令。这样不仅增强了脚本的功能性,还提高了使用的便利性,适应了最新版本 OpenSSH 服务器的安全要求。
总结
通过上述步骤,我们能够解决由于 OpenSSH 仅支持 ssh-ed25519
公钥算法而导致的 Python 脚本无法连接的问题。确保使用最新版本的 Paramiko 和正确配置公钥算法,可以保证脚本在更安全的 SSH 环境中正常运行。