使用 Paramiko 执行 SSH


# 使用 Paramiko 执行 SSH

# 关于 SSH

SSH 是一种网络协议,用于远程访问,并管理一个或多个设备。SSH 使用公钥加密来实现安全性。Telnet 和 SSH 之间的重要区别在于 SSH 使用加密,这意味着通过网络传输的所有 SSH 数据都可以防止未经授权的实施拦截。

SSH 基于 TCP,默认端口号为 22。在终端运行 ssh 命令以连接远程服务器命令如下:

ssh host_name@host_ip_address
1

# 关于 Paramiko

Paramiko 是一个基于 Python 编写的、使用 SSH 协议的模块,跟 Xshell 和 Xftp 功能类似,支持加密与认证,可以上传下载和访问服务器的文件。

可以利用 Paramiko 模块写服务器脚本,在本地执行,比如持续更新代码,查看日志,批量配置集群等。

Paramiko 主要包含 SSHClient 和 SFTPClient 两个组件,分别用来执行命令和实现远程文件操作。

由于 Paramiko 是 Python 的一个第三方库,首先需要安装它:

sudo pip install paramiko
1

# 用法示例

下面是一个使用 Paramiko 连接远程设备的示例程序(paramiko_example.py (opens new window)):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
使用第三方库:paramiko
"""

import time
import paramiko
from paramiko.ssh_exception import NoValidConnectionsError
from paramiko.ssh_exception import AuthenticationException


def do_ssh(host, username, password, commands):
    client = paramiko.SSHClient()
    # 如果是之前没有连接过的 ip,会出现选择 yes 或者 no 的操作
    # 自动选择 yes
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        client.connect(hostname=host, port=22, username=username, password=password)
    except NoValidConnectionsError:
        print('{host} 无法连接'.format(host=host))
    except AuthenticationException:
        print('{host} 密码错误'.format(host=host))
    except Exception as e:
        print(repr(e))
    else:
        # 执行操作
        for command in commands:
            stdin, stdout, stderr = client.exec_command(command)
            # 获取命令执行的结果
            for line in stdout:
                print(line.strip('\n'))
        """ 使用 send 方式
        # 在 SSH server 端创建一个交互式的 shell
        shell = client.invoke_shell()
        time.sleep(0.2)
        for command in commands:
            # 利用 send 函数发送 cmd 到SSH server,添加 '\n' 做回车来执行 shell 命令
            # 注意不同的情况,如果执行完 telnet 命令后,telnet 的换行符是 '\r\n'
            command += '\n'
            # 利用 send 函数发送 command 到 SSH server
            shell.send(command)
            time.sleep(0.2)
            if shell.recv_ready():
                # .recv(bufsize) 通过 recv 函数获取回显
                stdout = ssh.recv(1024)
                print(stdout.decode('utf-8'))
        """
    finally:
        # 关闭连接
        client.close()


if __name__ == "__main__":
    host = '192.168.10.x'
    username = 'admin'
    password = '******'
    commands = ['hostname', 'ls']
    do_ssh(host, username, password, commands)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

Paramiko 模块里上面代码中用到的方法介绍:

  • set_missing_host_key_policy(policy):设置连接的远程主机没有本地主机密钥时的策略。目前支持三种:RejectPolicy(the default),AutoAddPolicy,WarningPolicy
  • exec_command():该函数是将服务器执行完的结果一次性返回给你。
  • invoke_shell():也可以使用该函数来执行命令,它类似 shell 终端,可以将执行结果分批次返回,看到任务的执行情况,不会因为执行一个很长的脚本而不知道是否执行成功。

# 非交互式与交互式场景

exec_command 用于非交互式的场景,每次调用该方法就相当于重新开启了一个 command 窗口结束后就关闭了该窗口。

它的问题是无法连续进行操作,比如用该方法远程无法操作类似 python 或者 mysql 这样的 shell 窗口,一旦在 exec_command() 中输入类似 python 的指令,实际上会进入一个新的上下文环境以供输入 Python 特有的语法,但这个方法做不到这一点。

import time
import paramiko

hostname = '192.168.10.x'
port = 22
username = 'admin'
password = '******'
timeout = 10
commands = ['pwd', 'ls -l']
 
if __name__ == "__main__":
    # 实例化 SSHClient
    client = paramiko.SSHClient()
    # 自动添加策略,保存服务器的主机名和密钥信息
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        # 连接 SSH 服务器,以用户名和密码进行认证
        client.connect(hostname=hostname, port=port, username=username, password=password, timeout=timeout)
    except Exception as e:
        print(e)
        exit(1)
    for command in commands:
        # 执行命令
        stdin, stdout, stderr = client.exec_command(command)
        # 打印结果
        result = stdout.read().decode('utf-8'))
        err = stderr.read().decode('utf-8')
        if len(err) != 0:
            print(err)
        else:
            print(result)
    # 关闭 SSHClient
    client.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

invoke_shell 用于交互式场景,它类似 shell 终端,可以跟使用 SSH 终端客户端登录一样并使用终端仿真功能。如果想实现真正的交互式,就要使用 invoke_shell() 的方式。

import time
import paramiko

hostname = '192.168.10.x'
port = 22
username = 'admin'
password = '******'
timeout = 10
commands = ['pwd', 'ls -l']
 
if __name__ == "__main__":
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        client.connect(hostname=hostname, port=port, username=username, password=password, timeout=timeout)
    except Exception as e:
        print(e)
        exit(1)
    # 创建一个交互式的 shell
    shell = client.invoke_shell()
    for command in commands:
        # 添加 '\n' 做回车来执行 shell 命令
        # 注意不同的情况,如果执行完 telnet 命令后,telnet 的换行符是 '\r\n'
        command += '\n'
        # 利用 send 函数发送 command 到 SSH server
        shell.send(command)
        time.sleep(0.2)
        # 检查通道的数据是否准备好读取,即数据是否被缓冲
        if shell.recv_ready():
            # .recv(bufsize) 通过 recv 函数获取回显
            stdout = shell.recv(1024)
            print(stdout.decode('utf-8'))
    # 关闭 SSHClient
    client.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 总结

上面的示例程序我们通过 paramiko 模块连接了一台远程服务器,执行命令后打印了输出结果。

其中,invoke_shell 使用的是 shell channel,目的是实现交互式 shell 会话,就像使用 SSH 终端客户端登录一样并使用终端仿真功能;而 exec_command 的目的是使命令执行自动化,将命令作为「参数」,通过用户的默认 shell 程序,而不是作为「登录」shell 程序,这是主要的不同。

实际环境中在建立 SSH 连接后,我们可以在远程设备上执行所需的任何配置或操作。

# 参考资料

(完)