Python 自动化运维实现ssh 远程登录管理设备

实验一

  • 用Python实现SSH登陆单个交换机(192.168.250.2),为其loop0端口配置2.2.2.2/24这个IP。

网络拓扑搭建

在这里插入图片描述

<Huawei>sys
[Huawei]sys R2
[R2]int g0/0/0
[R2-GigabitEthernet0/0/0]ip add 192.168.250.2 24
[R2]stelnet server enable
[R2]aaa
[R2-aaa]local-user admin privilege level 3 password cipher Huawei@123
[R2-aaa]local-user admin service-type ssh
[R2-aaa]q
[R2]user-interface vty 0 4
[R2-ui-vty0-4]authentication-mode aaa
[R2-ui-vty0-4]protocol inbound ssh

源代码

import paramiko
import time

username = 'admin'
password = 'Huawei@123'
ip = '192.168.250.2'

def ensp_exec_command(command):
    # 创建SSH对象
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 连接服务器
    ssh.connect(hostname=ip, port=22, username=username, password=password)
    # 执行命令
    print("Sucessfully login to ", ip)

    command = ssh.invoke_shell()  # 激活terminal
    command.send("sys\n")
    command.send("int g0/0/0\n")
    command.send("ip address 2.2.2.2 255.255.255.0\n")

    time.sleep(5)  # 如果程序执行的太快,没有等到返回足够的信息,chan.recv(65535)不能得到想要的结果
    output = command.recv(65535)
    print(output.decode().strip())
    ssh.close()

if __name__ == '__main__':
    ensp_exec_command("dis cu")

结果:
Sucessfully login to  192.168.250.2
-----------------------------------------------------------------------------     
  User last login information:     
  -----------------------------------------------------------------------------
  Access Type: SSH      
  IP-Address : 192.168.250.10 ssh     
  Time       : 2020-05-24 00:03:22-08:00     
  -----------------------------------------------------------------------------
<R2>sys
Enter system view, return user view with Ctrl+Z.
[R2]int g0/0/0
[R2-GigabitEthernet0/0/0]

实验二

  • 配合getpass模块和input()函数实现交互式的SSH用户名和密码输入。
  • 配合for loop同时给5台交换机配置VLAN 10至VLAN 20。

网络拓扑搭建

在这里插入图片描述

<Huawei>sys
[Huawei]sys SW2
[SW2]
[SW2]int vlanif 1
[SW2-Vlanif1]ip add 192.168.250.2 24

SW3:
interface Vlanif1
 ip address 192.168.250.3 255.255.255.0

SW4:
interface Vlanif1
 ip address 192.168.250.4 255.255.255.0
SW2:
[SW2]stelnet server enable
[SW2]aaa
[SW2-aaa]local-user admin privilege level 3 password cipher Huawei@123
[SW2-aaa]local-user admin service-type ssh
[SW2-aaa]q
[SW2]user-interface vty 0 4
[SW2-ui-vty0-4]authentication-mode aaa
[SW2-ui-vty0-4]protocol inbound ssh

SW3\SW4:
stelnet server enable
#
aaa
 local-user admin password cipher Huawei@123
 local-user admin privilege level 3
 local-user admin service-type ssh
#
user-interface vty 0 4
 authentication-mode aaa
 protocol inbound ssh
#

我发觉配完之后,ssh登不上,the connection is closed by SSH server,看来交换机和路由器的ssh有点区别,然后查找官方交换机手册发现了这个,而且两种方案只能选择其一

方案1:aaa配了之后,只配ssh authentication-type default password;
方案2:aaa配了之后,配置具体的用户,用户的服务类型和认证方式
例如:ssh user xx;
ssh user xx service-type stelnet;
ssh user xx authentication-type password;

方案一:
[SW2]ssh authentication-type default

方案二:
[SW2]ssh user admin
Info: Succeeded in adding a new SSH user.
[SW2]ssh user admin service-type stelnet
[SW2]ssh user admin authentication-type password

源代码

import paramiko
import time
#import getpass

username = input('Username: ')
password = input('Password: ')
# password = getpass.getpass('Password: ') #在PyCharm中,运行该命令是无效的会卡在那。需要在terminal中运行。

for i in range(2, 5):
    ip = "192.168.250." + str(i)
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh_client.connect(hostname=ip,port=22,username=username,password=password)
    print("\033[31;1m[+] Successfully connect to \033[0m", ip)
    command = ssh_client.invoke_shell()
    command.send("system-view\n")
    for n in range (10, 21):
        print("\033[32;1m[*]Creating VLAN[*]\033[0m" + str(n))
        command.send("vlan " + str(n) + "\n")
        time.sleep(0.5)
    command.send("return\n")
    time.sleep(2)
    output = command.recv(65535)
    print(output.decode().strip())

ssh_client.close()

结果如图:
在这里插入图片描述

实验三

  • 在生产环境中,交换机的管理ip地址基本不可能像实验环境中这样11到15连续的,有些交换机的管理ip甚至在不同的网段,这种情况下,我们就不能简单的用for loop来循环ip地址的最后一段来登录交换机了。这里我们要额外开一个文本文件,把我们需要登录的交换机ip全部写进去,然后用for loop配合open()函数来批量登录所有交换机。
  • 自行准备实验前提:
  1. 创建一个名为ip_list.txt的文件,把S2,S3,S4交换机的管理IP地址放进去
  2. 把S4的管理地址从192.168.250.4改成192.168.250.44

网络拓扑搭建

和实验二的网络拓扑和配置一样的,唯独就是地址变成192.168.250.44
在这里插入图片描述

源代码

import paramiko
import time
import getpass

username = input('Username: ')
password = input('Password: ')
# password = getpass.getpass('Password: ') #在PyCharm中,运行该命令是无效的会卡在那。需要在terminal中运行。

f = open("ip_list.txt","r")
for line in f.readlines():
    ip = line.strip()
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh_client.connect(hostname=ip,port=22,username=username,password=password)
    print("\033[31;1m[+] Successfully connect to \033[0m", ip)
    command = ssh_client.invoke_shell()
    # 配置trunk口
    command.send("system-view\n")
    command.send("int g0/0/1\n")
    command.send("port link-type trunk\n")
    command.send("port trunk allow-pass vlan all\n")
    time.sleep(2)
    output = command.recv(65535)
    print(output.decode().strip())
    print("\033[32;1m[*]配置结束[*]\033[0m")

f.close()
ssh_client.close()


结果如图:
在这里插入图片描述

实验四

在只有几台设备的实验环境中常用。但是在有成千上万台网络设备需要管理的生产环境中,实验三这种方法显得很笨拙,缺乏灵活性。举例来说,假设你的生产环境中有不同型号,不同操作系统,不同命令格式的设备各1000台

比如华为防火墙的5500和6000

你必须反复修改command.send()这个部分的代码,如果只是简单数条命令还好办,一旦遇到大规模的配置,那这种方法的效率会很低下。

解决这个问题的思路是分别创建两个文本文件,一个用来存放配置5500要用到的命令集,另一个用来存放配置6000要用到的命令集,然后在Python脚本里通过for循环加open()来读取两个文件里的内容以达到分别给所有5500和6000 防火墙 配置的目的,这样做的好处是无须修改command.send()这个部分的代码,因为所有的命令行已经在文本文件里预先设置好了。

不过这时新的问题又来了,每次配备不同型号的设备,我们必须手动修改open()函数所打开的配置文本文件,比如在给5500做配置的时候,我们要open(‘command_5500.txt’), 给6000做配置的时候,我们又要open(‘command_6000.txt’),这样一来二去修改配置脚本的做法大大缺乏灵活性。

如果说只有两种不同型号,不同命令格式的设备还能应付的话,那么当你的生产环境中有多种型号的设备,甚至其他厂商的设备,而你又接到任务要对所有这些设备同时修改某个共有的配置(比如网络新添加了某台服务器,要统一给所有设备修改AAA配置,亦或者网络新添加了某台NMS系统,要统一给所有设备修改SNMP配置),因为不同OS的配置命令完全不同,这时你就能体会到痛苦了。这时可以用到sys.argv来解决这个问题。

# -- coding: UTF-8 --
import paramiko
import time
# import getpass
import sys

username = input('Username: ')
password = input('Password: ')
# password = getpass.getpass('password: ')
ip_file = sys.argv[1]
cmd_file = sys.argv[2]

iplist = open(ip_file, 'r')
for line in iplist.readlines():
    ip = line.strip()
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh_client.connect(hostname=ip, username=username, password=password)
    print("You have successfully connect to ", ip)
    command = ssh_client.invoke_shell()
    # 读取命令
    cmdlist = open(cmd_file, 'r')
    cmdlist.seek(0)
    for line in cmdlist.readlines():
        command.send(line + "\n")
        time.sleep(2)
    cmdlist.close()
    output = command.recv(65535)
    print(output.decode().strip())
    print("\033[32;1m[*]配置结束[*]\033[0m")

iplist.close()
ssh_client.close
  • 因为要用到sys.argv,所以这里import了sys模块。
  • 「argv」是「argument variable」参数变量的简写形式,这个变量返回的是一个列表,argv[0] 一般是被调用的脚本的文件名或全路径,从argv[1]开始就是传入的数据了

举个例子,可以在终端运行

$ python lab4.py ip_3750.txt cmd_3750.txt
这时argv = ['lab4.py', 'ip_3750.txt', 'cmd_3750.txt']
我们代码里的:
ip_file = sys.argv[1],此时也就等同于ip_file = ip_3750.txt
cmd_file = sys.argv[2], 此时也就等同于cmd_file = cmd_3750.txt
同理,如果这时我们在linux执行命令:
$ python lab4.py ip_3850.txt cmd_3850.txt
那么此时ip_file = ip_3850.txt, cmd_file = cmd_3850.txt
  • 由此可见,配合sys.argv,我们可以很灵活地选用我们脚本需要调用的文本文件,而无需反反复复地修改脚本代码。
    在这里插入图片描述

实验五

在网络设备数量超过千台甚至上万台的大型企业网中,难免会遇到某些设备管理IP不通,SSH连接失败的情况,设备数量越多,这种情况发生的几率越大。这个时候如果你想用Python批量配置所有的设备,就一定要注意这种情况,很有可能你的脚本跑了还不到一半就因为中间某一个连接不通的设备而停止了。比如说你有3000台交换机需要统一更改本地用户名和密码,前500台交换机的连通性都没问题,第501台交换机因为某个网络问题导致管理IP不可达,SSH连不上了,这个时候Python会返回一个socket.error:
socket.error: [Errno 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has

同样的问题也会发生在当你输入了错误的交换机用户名和密码之后,或者某些交换机和其他大部分交换机用户名和密码不一致的时候(因为我们只能输入一次用户名和密码,用户名/密码不一致会导致个别交换机无法登陆的情况发生),也许你会问大型企业网不都是统一配置AAA配合TACACS或者RADIUS做用户访问管理吗,怎么还会出现登陆账号和密码不一样的问题?历史服务器遗留下来的问题,这就出现了两套账号和密码的情况,后果就是使用paramiko来SSH登陆网络设备的python会返回认证异常
paramiko.ssh_exception.AuthenticationException:

要解决上述两个问题也很简单,这里我们可以用到Python中的try, except异常处理语句,来让你的脚本在遇到上述出现设备连通性故障以及用户认证无法通过的情况时,继续给后面的设备做配置,而不会因为某个故障的设备而停下来。

import paramiko
import time
import getpass
import sys
import socket

username = input('Username: ')
password = input('Password: ')
# password = getpass.getpass('password: ')
ip_file = sys.argv[1]
cmd_file = sys.argv[2]

switch_with_authentication_issue = []
switch_not_reachable = []

iplist = open(ip_file, 'r')
for line in iplist.readlines():
    try:
        ip = line.strip()
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(hostname=ip, username=username, password=password, look_for_keys=False)
        print("You have successfully connect to ", ip)
        command = ssh_client.invoke_shell()
        cmdlist = open(cmd_file, 'r')
        cmdlist.seek(0)
        for line in cmdlist.readlines():
            command.send(line + "\n")
        time.sleep(2)
        cmdlist.close()
        output = command.recv(65535)
        print(output.decode().strip())
        print("\033[32;1m[*]配置结束[*]\033[0m")
    except paramiko.ssh_exception.AuthenticationException:
        print("\033[31;1mUser authentication failed for \033[0m" + ip + ".")
        switch_with_authentication_issue.append(ip)
    except socket.error:
        print(ip + "\033[31;1m is not reachable.\033[0m")
        switch_not_reachable.append(ip)

iplist.close()
ssh_client.close()

print('\n\033[31;1m[+]User authentication failed for below switches: \033[0m')
for i in switch_with_authentication_issue:
    print(i)

print('\n\033[31;1m[+]Below switches are not reachable: \033[0m')
for i in switch_not_reachable:
    print(i)
  • 创建两个空列表,取名为switch_with_authentication_issue和switch_not_reachable, 分别在脚本最后配合for循环来统计有哪些设备是因为认证问题无法登录,有哪些是因为设备本身不可达而无法登录。
  • 在for循环下使用try-except, 用excpet paramiko.ssh_exception.AuthenticationException:来应对用户名/密码不匹配时返回的错误代码,将出现该错误的交换机的管理IP地址用.append(ip)方法放入switch_with_authentication_issue这个列表中,同理用except socket.error:来应对交换机不可达时返回的错误代码,将出现该错误的交换机的管理IP地址用append(ip)方法放入switch_not_reachable这个列表中。

补充 dis cu

像dis cu这种一个屏幕显示不了的,需要按回车才能显示全的操作

    # 定义一个集合,每一屏的信息都追加到集合中
    result_list = []
    # 判断是否有--More--
    command = "dis cu\n"
    tn.write(command.encode('ascii'))
    time.sleep(2)
    while (True):
        # 获取返回的结果
        command_result = tn.read_very_eager().decode("ascii")
        # 追加到集合中
        result_list.append(command_result)

        # 判断当前这一页是否 --More-- 结束
        if '  ---- More ----' in command_result.strip():
            tn.write(b"\n")
            time.sleep(0.5)
        else:
            break

    # 拼接集合
    result_str = "\n".join(result_list)
    print(result_str)

    tn.close()
    print("测试完毕")

猜你喜欢

转载自blog.csdn.net/qq_39578545/article/details/106314491