一、Paramiko库简介

        Paramiko 是一个Python环境下实现SSHv2操作的模块

        支持以口令认证和公钥认证的远程连接

        可执行远程命令,安全文件传输,端口转发与隧道

        支持Linux、Windows、MacOS等系统,安全性高,功能全面

        实现运维自动化的一个工具

二、Paramiko组件

        协议类:

                Channel

                Client

                Message

                Packetizer

                Transport

        以上几个类是Paramiko的核心组件,下面对Channel和SSHClient进行一些说明:

        1、Channel 类

                一个基于SSH传输的安全隧道。Channel 的行为类似于套接字,其API应与Python的套接字API无异。

                主要用于创建在 SSH Transport 上的安全通道。

                由于SSHv2采用基于窗口的流量控制,如果停止从 Channel 接收数据,且其缓冲区已满,服务器将无法继续发送数据,直接读取部分数据为止,但不会影响同一传输上的其他通道。如果服务器未读取接收到的数据,那么调用 send 方法可能会阻塞,除非设置了超时。

(根据Paramiko官方文档翻译整理,如有疏漏敬请指正!)

               Channel的方法有很多,对于我来说常用的也就两、三个:

                        send()

                                发送数据

                        recv(nbytes)

                                接收数据

                                nbytes:一次接收的最大数量

                        close()

                                关闭通道

        2、Client类

                SSHClient

                与SSH服务器之间会话的高级表示。封装了 Transport、Channel、SFTPClient。负责处理身份验证和打开通道的大部分工作

                (1)密钥相关方法:

方法 说明
load_system_host_keys() 以只读的方式加载密钥
load_host_keys() 以读写的加载密钥
save_host_keys() 将密钥保存至文件
get_host_keys() 加载本地的密钥,用于检查或修改本地密钥
set_missing_host_key_policy()

设备在连接没有已知密钥的服务器时所使用的策略:

RejectPolicy:默认策略,当密钥不在已知列表中时,直接拒绝连接,并抛出SSHException

AutoAddPolicy:自动将未知服务器的密钥添加到本地密钥集合中,然后继续连接

WarningPolicy:当遇到未知服务器密钥时,记录告警日志,接受密钥并继续连接

                        Python 内部维护一个密钥集合。load_host_keys()会把密钥加载到这个集合中,并且这些密钥被认为是可写的。

                        当修改这个集合再调用save_host_keys(),Paramiko会把整个集合写入指定文件中。

                        load_system_host_keys()加载的密钥为只读,save_host_keys()不会将密钥写回到文件中,能够使用系统已知的密钥进行验证,又能单独管理自己的密钥文件

                (2)connect()

                        用于连接到SSH服务器并进行身份验证。

connect(
    hostname,        # 要连接的 SSH 服务器的主机名或IP地址,必填
    port=22,         # SSH 服务器的端口号,选填,默认: 22,如果修改了端口则必填     
    username=None,     # 用户认证的用户名
    password=None,     # 用户认证的密码
    pkey=None,         # 用于认证的私钥对象,如RSAKey,与key_filename互斥
    key_filename=None, # 私钥文件的路径
    timeout=None,      # TCP 连接超时时间。控制建立 TCP 连接时最长等待时间
    allow_agent=True,  # 是否允许 使用运行中的 SSH agent 进行认证,默认为:True
    look_for_keys=True,     # 是否在 ~/.ssh/ 目录下搜索私钥文件,默认为:True
    compress=False,         # 是否启用压缩,默认:False
    sock=None,              # 一个已打开的套接字,用于替代直接创建新的TCP连接。可用于通过代理或已建立的隧道连接
    banner_timeout=None,    # 等待服务器返回 banner 的超时时间,默认与 timeout 相同
    auth_timeout=None,      # 认证阶段的超时时间,如果认证过程超时,则终止连接
    channel_timeout=None,   # 通道操作的超时时间。
    passphrase=None,        # 用于解密私钥文件的口令,如果密钥加密且未提供 passphrase,则提示输入
    disabled_algorithms=None,     # 用于禁用某些加密算法、密钥交换算法、MAC算法等。
    transport_factory=None,       # 用于自定义 Transport 类的实例
    auth_strategy=None            # 自定义认证策略。如不提供则按默认顺序尝试认证方式
    )

                        所有参数中,常用的不多:hostname、port、usname、password、pkey或key_filename、auto_timeout、timeout、look_for_keys

                        示例:

# 这是一段来自于 Paramiko 官方文档的一个示例

# 创建一个 SSHClient 实例
client = SSHClient()

# 从系统文件中加载主机密钥文件,连接服务器时,Paramiko可以自动验证服务器的主机密钥是否与本地记录匹配
client.load_system_host_keys()

# 连接到指定的 SSH 服务器
client.connect('ssh.example.com')

# 在远程服务器上执行命令:ls -l
stdin, stdout, stderr = client.exec_command('ls -l')

                (3)close()

                        关闭当前使用的SSHClient连接

                (4)exec_command()

                        作用:在远程服务器上执行一条命令,并获取到这条命令的输入、输出和挺不错信息。

                        远程Linux服务器时推荐使用。

exec_command(
    command,            # 要执行的命令
    bufszie=-1,         # 读写数据时的缓冲区大小。一般不作修改,默认即可
    timeout=None,       # 设置命令所在通道的超时时间,即等待命令执行完成的时间
    get_pty=False,      # 向服务器请求一个伪终端,默认不请求
    environment=None    # 一个设置了 shell 环境变量的字典,将被合并到远程命令执行的默认环境中
)

                        每条 exec_command() 都是独立的 shell 会话,每次只能执行一条命令,执行完命令后通道自动关闭。如果需要执行一连串的命令,可使用 && 或写成一个脚本。

                        get_pty=False:某些交互命令(sudo、top、password)需要终端环境才可以运行。普通命令不需要终端(ls、cat)。

                        exec_command()的返回值是一个三元组(stdin,stdout,stderr)

                                stdin:可以往里面写数据【.write()】,这些数据会作为命令的标准输入

                                stdout:命令的正常输出结果,可以用 .read() 或 .readlines() 读取

                                stderr:命令的错误输出

                (5)invoke_shell()

                        作用:在 SSH 服务器上启动一个交互式 Shell 会话。会话会打开一个新的通道,并使用指定的终端类型和大小将其连接到一个伪终端。

                        远程网络设备时推荐使用。

invoke_shell(
    term='vt100',        # 要模拟的终端类型
    width=80,            # 终端窗口的宽度,以字符数为单位
    heigth=24,           # 终端窗口的高度,以字符数为单位
    width_pixels=0,      # 终端窗口的宽度,以像素为单位
    height_pixels=0,     # 终端窗口的高度,以像素为单位
    environment=None     # 命令的环境变量
)

                        invoke_shell()和exec_command()的不同之处在于,invoke_shell()打开一个可持续交互的终端,可连接发送多条命令,并实时读取输出。

                

三、单台设备连接

        下面通过一个小实验来熟悉Paramiko的使用。

        1、一个简单的拓扑

                 AR1路由器通过G0/0/0接口连接主机,并配置接口IP地址,同时配置SSH,在这里我使用的是AAA 用户名和密码的方式进行验证。配置好后记得测试一下。确保SSH能正常登录。

               通过Paramiko模块SSH连接路由器,并给路由器配置一个 lookback 0 接口,IP地址为:10.1.1.1/32

        2、脚本内容

                接下来创建一个 .py 的脚本文件,脚本内容如下:

# 1、导入所需的模块
import paramiko
import time

# 创建一个 SSHClient 实例:ssh_client
ssh_client = paramiko.SSHClient()


# 设置缺失主机密钥的策略为:AutoAddPolicy()

ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 发起SSH连接
ssh_client.connect(hostname='192.168.44.100', username='admin', password='admin@123', look_for_keys=False)

# 调用invoke_shell() 方法请求远程服务器分配一个交互式 Shell 会话 
command = ssh_client.invoke_shell()
time.sleep(2)

# 使用通道的 send() 方法向远程 Shell 发送字符串指令
command.send("display ip interface brief\n")
time.sleep(1)
command.send("system-view\n")
command.send("interface loopback 0\n")
command.send("ip address 10.1.1.1 32\n")
command.send("display ip interface brief\n")
time.sleep(2)

# 通过 recv() 将返回的结果赋值给 result
result = command.recv(65535).decode("ASCII")
print(result)

# 关闭通道
command.close()
# 关闭SSHClient
ssh_client.close()

                为什么会用到 time 模块?

                在有些情况下(如网络延迟,设备对指令的执行等)需要一定的时间才能返回结果。同时send()发送的指令是执行完一条后接着继续执行下一条,直到所有send()全部发送完。像“display ip interface brief”指令需要等待一会儿才会显示出结果。send()本身没有等待时间,revc()可能会捕获不到回显结果。

                解决办法就是使用 time 模块的sleep()来进行硬性的时间等待。

        3、实验结果

                第一张图片显示的结果为没有使用sleep()捕获的结果。第二张图片为在connect()和发送第一条指令“display ip interface brief\n”处设置sleep()后的结果。第三张图片为在send()发送最后一条指令后添加sleep()后的结果。

                具体在哪条send()指令发送后需要使用sleep()需要根据实际情况来进行调整。

                同时会发现在脚本里send()每次发送的字符串指令后面都有一个换行符 \n。这是因为如果只是send('display ip interface brief')这样发送指令的话,它是没有执行的,就像我们平时通过超级终端连接上设备进行调试时一样。

四、多台设备连接

        如果我们同时要对多台设备进行日常巡检需要如何实现?

        我们可以为每台设备创建一个脚本文件,然后分别执行。但这种方式肯定不是最便捷的。

        整个网络中同一类型的设备基本上会使用相同的登录方式、账号、密码,唯一变动的是每台设备的IP地址不一样。那么就可以根据这个特点,将所有设备的IP地址定义为一个List列表,对List列表进行循环逐一获取设备的IP地址,然后对每台设备进行远程操作。

        1、实验拓扑

                3台路由器已配置好,均可通过SSH登录。

                需要远程连接这3台设备,然后执行'display ip interface brief’查看一些信息

        2、脚本

# 1、导入所需的模块
import paramiko
import time

ip_list = ['192.168.44.101', '192.168.44.105', '192.168.44.108']


for ip in ip_list:

    # 创建一个 SSHClient 实例:ssh_client
    ssh_client = paramiko.SSHClient()

    # 设置缺失主机密钥的策略为:AutoAddPolicy()

    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    # 发起SSH连接
    ssh_client.connect(hostname=ip, username='admin', password='admin@123', look_for_keys=False)

    # 调用invoke_shell() 方法请求远程服务器分配一个交互式 Shell 会话 
    command = ssh_client.invoke_shell()
    time.sleep(2)
    
    print(f"========== 登录设备" + ip + f" ==========")

    # 使用通道的 send() 方法向远程 Shell 发送字符串指令
    command.send("display ip interface brief\n")
    time.sleep(1)

    # 通过 recv() 将返回的结果赋值给 result
    result = command.recv(65535).decode("ASCII")
    print(result)

    # 关闭通道
    command.close()
    # 关闭SSHClient
    ssh_client.close()

        3、执行结果


更多推荐