[SW1]rsa local-key-pair create 
The key name will be: SW1_Host
The range of public key size is (512 ~ 2048). 
NOTES: If the key modulus is greater than 512, 
       it will take a few minutes.
Input the bits in the modulus[default = 512]:2048
Generating keys...
..........++++++++++++
..........++++++++++++
.++++++++
....++++++++

由于ubuntu 20.04是比较新的版本,不再支持1023bit以下长度的密钥,所以直接配置1024或者2048长度的密钥

使能Stelnet服务器

stelnet server enable

配置SSH

配置SSH用户登录界面

user-interface vty 0 4
 authentication-mode aaa
 user privilege level 15
 protocol inbound ssh

配置SSH用户

ssh user sht
ssh user sht authentication-type password
ssh user sht service-type stelnet

如果SSH用户使用password认证( authentication-type = password | rsa | password-rsa ),则只需要在SSH服务器端生成本地RSA密钥。如果SSH用户使用RSA认证,则在服务器端和客户端都需要生成本地RSA密钥对,并且服务器端和客户端都需要将对方的公钥配置到本地。

配置对SSH用户进行password认证

local-user sht password cipher Huawei local-user sht privilege level 15 local-user sht service-type ssh

在ubuntu 20.04上使用ssh sht@192.168.50.1,出现报错:

$ ssh sht@192.168.50.1
**Unable to negotiate with** 192.168.50.1 port 22: **no matching key exchange method found.** 
**Their offer: diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1**

问题描述:

如果客户端和服务器无法就一组共同的参数达成一致,那么连接将失败。OpenSSH (7.0及以上版本)会产生类似这样的错误信息,Ubuntu20.04使用OpenSSH 8.2。在这种情况下,客户端和服务器无法就密钥交换算法达成一致,服务器只提供(即该实验中的SW1)两个方法diffie-hellman-group1-sha1和diffie-hellman-group-exchange-sha1,OpenSSH支持这两种密钥交换方法,但是默认不启用,因为这两种方法版本较久,比较弱(weak),在所谓的Logjam攻击的理论范围内。所以我们需要在客户端启用较弱的方法。

$ ssh -V
OpenSSH_8.2p1 Ubuntu-4ubuntu0.1, OpenSSL 1.1.1f  31 Mar 2020

解决方案:

OpenSSH: Legacy Options

$ ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 sht@192.168.50.1
  • + — 在客户端默认集合中增加该交换算法而不是替换默认算法
  • -o — 选项
  • -KexAlgorithms — 密钥交换方法-用来生成每个连接的密钥;
  • 使用ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 sht@192.168.50.1后,成功连接上SW1

    $ ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 sht@192.168.50.1
    The authenticity of host '192.168.50.1 (192.168.50.1)' can't be established.
    RSA key fingerprint is SHA256:BqJmFBxfyFuobgWlC7MmLlpjfu1UjsyhnLbdd/GIVc8.
    Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
    Warning: Permanently added '192.168.50.1' (RSA) to the list of known hosts.
    sht@192.168.50.1's password: 
    Info: The max number of VTY users is 5, and the number
          of current VTY users on line is 1.
          The current login time is 2021-03-02 19:12:58.
    <SW1>sys
    Enter system view, return user view with Ctrl+Z.
    [SW1]
    

    配置paramiko_test.py对SW1进行配置

    编写脚本paramiko_test.py

    import paramiko
    import time
    ip_address = "192.168.50.1"
    username = "sht"
    password = "Huawei"
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # not recommended for production but fine for labs
    ssh_client.connect(hostname = ip_address, username = username, password = password)
    print("Successful connection", ip_address)
    remote_connection = ssh_client.invoke_shell()
    remote_connection.send("system\n")
    # creation of vlans
    for n in range (2,21):
        print("Creating VLAN " + str(n))
        remote_connection.send("vlan " + str(n) +  "\n")
        remote_connection.send("description VLAN " + str(n) +  "\n")
        time.sleep(0.5) # wait half of second
    remote_connection.send("return\n")
    time.sleep(1)
    output = remote_connection.recv(65535)
    print(output.decode('ascii'))
    ssh_client.close
    

    paramiko_test.py脚本解释

    ip_address = "192.168.50.1"
    username = "sht"
    password = "Huawei"
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # not recommended for production but fine for labs
    ssh_client.connect(hostname = ip_address, username = username, password = password)
    

    Paramiko官方文档给出的paramiko.client.SSHClient类的描述为:

    与SSH服务器建立SSH会话的高级表示,该类封装了TransportChannelSFTPClient类来处理好认证和打开通道(channel)。

    SSHClient的典型使用方式为:

    client = SSHClient()
    client.load_system_host_keys()
    client.connect('ssh.example.com')
    # stdin, stdout, stderr = client.exec_command('ls -l') 暂时不用了解
    

    使用SSHClient()创建SSH客户端,上传脚本的一端作为服务端,然后调用load_system_host_keys(filename = None)读取主机密钥,参数filename默认为None,则会尝试从用户的本地 "known hosts"文件中读取密钥;再调用connect()hostnamessh.example.com的服务器建立连接,服务器的主机密钥会与系统主机密钥 (load_system_host_keys) 和任何本地主机密钥 (load_host_keys) 对照检查,如果在这两组主机密钥中都找不到服务器的主机名,则使用缺失主机密钥策略 (set_missing_host_key_policy),默认策略是决绝连接并报错SSHException

    set_missing_host_key_policy(paramiko.AutoAddPolicy( ))

  • set_missing_host_key_policy(policy) 设置连接到未知主机密钥的服务器时要使用的策略;policy即要设置的策略类,可以设置为RejectPolicy(默认)、AutoAddPolicy和WarningPolicy等;如果不配置该策略,默认是拒绝与服务器连接的
  • paramiko.AutoAddPolicy() 设为参数,用于自动将主机名和新的主机密钥添加到本地HostKeys对象中,并将该对信息保存,该方法由SSHClient调用;
  • 不仅接受了来自未知主机密钥的服务器提供的公钥,建立了连接,还会将主机名与对应的密钥保存到HostKeys对象中,后面再与该服务器进行连接,就可以使用load_system_host_keysload_host_keys方法了
  • remote_connection = ssh_client.invoke_shell()
    remote_connection.send("system\n")
    # creation of vlans
    for n in range (2,21):
        print("Creating VLAN " + str(n))
        remote_connection.send("vlan " + str(n) +  "\n")
        remote_connection.send("description VLAN " + str(n) +  "\n")
        time.sleep(0.5) # wait half of second
    remote_connection.send("return\n")
    
  • remote_connection = ssh_client.invoke_shell()
  • 由于SSHClient类封装了Channel类,类似socket,使用invoke_shell()后,客户端会在SSH服务器上启动一个交互式shell会话,即用通道类(Channel)新建通道(channel),并按要求的终端类型和大小连接到一个伪终端
  • ssh_client是SSHClient类的对象,调用SSHClient的方法invoke_shell()后,创建了remote_connection这个通道类(Channel)的通道
  • remote_connection.send()
  • 通道(Channel)类中的send()用于传输数据
  • 这里remote_connection是通道(channel),调用send()方法向SSH服务器发送数据
  • output = remote_connection.recv(65535) 中,remote_connection通道使用recv(65535)设置读取Bytes的最大值;recv()如果返回字节的长度为0时,通道流就会被关闭;

    output.decode('ascii')recv()的返回值为字节型字符串,将返回值赋值给output变量,然后在Python 3中使用decode('ascii')将字节型字符串解析为ascii编码,帮助输出美观的内容;

    如果不适用decode('ascii'),输出如下:

    ssh_client.close 关闭ssh_clientclose直接关闭SSHClient类

    local-user sht password cipher Huawei local-user sht privilege level 15 local-user sht service-type ssh ssh user sht ssh user sht authentication-type password ssh user sht service-type stelnet vlan 100 management-vlan interface Vlanif100 ip address 192.168.50.x 255.255.255.0

    SwitchA | SwitchB

    interface Ethernet0/0/1 port link-type trunk port trunk allow-pass vlan 100 interface Ethernet0/0/2 port link-type trunk port trunk allow-pass vlan 100 interface Ethernet0/0/3 port link-type trunk port trunk allow-pass vlan 100

    SwitchC | SwitchD

    interface Ethernet0/0/1 port link-type trunk port trunk allow-pass vlan 100 interface Ethernet0/0/2 port link-type trunk port trunk allow-pass vlan 100

    验证Ubuntu与各交换机的连通性

    $ ssh-keygen -f "/home/sht/.ssh/known_hosts" -R "192.168.50.100"
    # $ ssh-keygen -f "/home/sht/.ssh/known_hosts" -R "192.168.50.101"
    # $ ssh-keygen -f "/home/sht/.ssh/known_hosts" -R "192.168.50.102"
    # $ ssh-keygen -f "/home/sht/.ssh/known_hosts" -R "192.168.50.103"
    

    如果都能成功登录,则说明Ubuntu与交换机连通性良好,并且可以进行远程登录;

    编写脚本并上传到设备

    配置内容总览

    #!/usr/bin/env python3
    import paramiko
    import time
    SwitchA = {
        'ip': '192.168.50.100',
        'username': 'sht',
        'password': 'Huawei',
        'is_root': True,
        'primary_instances': ['1'],
        'secondary_instances': ['2'],
        'cost_change_interfaces': [],
        'trunk_interfaces': ['e0/0/1', 'e0/0/2'],
        'access_interfaces': [],
        'root_interfaces':['e0/0/2'],
    SwitchB = {
        'ip': '192.168.50.101',
        'username': 'sht',
        'password': 'Huawei',
        'is_root': True,
        'primary_instances': ['2'],
        'secondary_instances': ['1'],
        'cost_change_interfaces': [],
        'trunk_interfaces': ['e0/0/1', 'e0/0/2'],
        'access_interfaces': [],
         'root_interfaces':['e0/0/2'],
    SwitchC = {
        'ip': '192.168.50.102',
        'username': 'sht',
        'password': 'Huawei',
        'is_root': False,
        'primary_instances': ['1'],
        'secondary_instances': ['2'],
        'cost_change_interfaces': ['e0/0/2'],
        'trunk_interfaces': ['e0/0/1', 'e0/0/2'],
        'access_interfaces': ['e0/0/3'],
        'root_interfaces':[],
    SwitchD = {
        'ip': '192.168.50.103',
        'username': 'sht',
        'password': 'Huawei',
        'is_root': False,
        'primary_instances': ['2'],
        'secondary_instances': ['1'],
        'cost_change_interfaces': ['e0/0/2'],
        'trunk_interfaces': ['e0/0/1', 'e0/0/2'],
        'access_interfaces': ['e0/0/3'],
        'root_interfaces':[],
    def paramiko_SSH_Command(switch):
    	''' Start '''
    	ssh_client = paramiko.SSHClient()
    	ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # not recommended for production but fine for labs
    	ssh_client.connect(hostname = switch['ip'], username = switch['username'], password = switch['password'])
    	print("               "*3)
    	print("==============="*3)
    	print("               "*3)
    	print("Successful connection", switch['ip'])
    	remote_connection = ssh_client.invoke_shell()
    	remote_connection.send("system\n")
    	''' Some Configuration Functions '''
    	common_Configuration(remote_connection, switch['cost_change_interfaces'], switch['secondary_instances'])
    	if switch['is_root']:
    		root_PriSec(remote_connection, switch['primary_instances'], switch['secondary_instances'])
    	edged_port_bpdu_protection(remote_connection, switch['access_interfaces'])
    	if switch['root_interfaces']:
    		root_protection(remote_connection, switch['root_interfaces'])
    	conf_trunk_interfaces(remote_connection, switch['trunk_interfaces'])
    	if switch == SwitchC:
    		conf_access_interfaces(remote_connection, switch['access_interfaces'], 2)
    	elif switch == SwitchD:
    		conf_access_interfaces(remote_connection, switch['access_interfaces'], 11)
    	''' End '''
    	remote_connection.send("return\n")
    	remote_connection.send("save\n")
    	remote_connection.send("y\n")
    	remote_connection.send("\n")
    	time.sleep(5)
    	output = remote_connection.recv(65535)
    	print(output.decode('ascii'))
    	ssh_client.close
    def common_Configuration(remote_connection, cost_change_interfaces, secondary_instances):
    	# Stp region_configuration
    	print("Configuring stp region_configuration ...")
    	region_configurations = ['stp region-configuration\n', 'region-name RG1\n', 'instance 1 vlan 2 to 10\n',
    		'instance 2 vlan 11 to 20\n', 'active region-configuration\n', 'quit\n']
    	for region_configuration in region_configurations:
    		remote_connection.send(region_configuration)
    	# Stp cost computing methods
    	print("Configuring stp pathcost-standard legacy ...")
    	remote_connection.send("stp pathcost-standard legacy\n")
    	# Stp interface cost
    	print("Configuring interface cost ...")
    	if cost_change_interfaces:
    		for secondary_instance in secondary_instances:
    			for cost_change_interface in cost_change_interfaces:
    				remote_connection.send("interface " + cost_change_interface + "\n")
    				remote_connection.send("stp instance " + secondary_instance +" cost 20000\n")
    				remote_connection.send("quit\n")
    	# STP enable
    	print("Creating VLAN and Enabling STP ...")
    	remote_connection.send("vlan batch 2 to 20\n")
    	remote_connection.send("stp enable\n")
    def conf_trunk_interfaces(remote_connection, trunk_interfaces):
    	print("Configuring trunk interfaces ...")
    	for trunk_interface in trunk_interfaces:
    		remote_connection.send("interface " + trunk_interface + "\n")
    		remote_connection.send("port link-type trunk\n")
    		remote_connection.send("port trunk allow-pass vlan 2 to 20\n")
    		remote_connection.send("quit\n")
    def conf_access_interfaces(remote_connection, access_interfaces, access_vlan):
    	print("Configuring access interfaces ...")
    	for access_interface in access_interfaces:
    		remote_connection.send("interface " + access_interface + "\n")
    		remote_connection.send("port link-type access\n")
    		remote_connection.send("port default vlan " + str(access_vlan) + "\n")
    		remote_connection.send("quit\n")
    def root_protection(remote_connection, interfaces):
    	print("Configuring stp protection ...")
    	for interface in interfaces:
    		remote_connection.send("interface " + interface + "\n")
    		remote_connection.send("stp root-protection\n")
    		remote_connection.send("quit\n")
    def edged_port_bpdu_protection(remote_connection, interfaces):
    	print("Configuring edged-port and bpdu protection ...")
    	if interfaces:
    		for interface in interfaces:
    			remote_connection.send("interface " + interface + "\n")
    			remote_connection.send("stp edged-port enable\n")
    			remote_connection.send("quit\n")
    		remote_connection.send("stp bpdu-protection\n")
    def root_PriSec(remote_connection, primary_instances, secondary_instances):
    	# Stp primary root
    	print("Configuring primary root ...")
    	if primary_instances:
    		for primary_instance in primary_instances:
    			remote_connection.send("stp instance " + primary_instance + " root primary\n")
    	else:
    		print("No primary root configuration.")
    	# Stp secondary root
    	print("Configuring secondary root ...")
    	if secondary_instances:
    		for secondary_instance in secondary_instances:
    			remote_connection.send("stp instance " + secondary_instance + " root secondary\n")
    	else:
    		print("No secondary root configuration.")
    if __name__=="__main__":
    	paramiko_SSH_Command(SwitchA)
    	paramiko_SSH_Command(SwitchB)
    	paramiko_SSH_Command(SwitchC)
    	paramiko_SSH_Command(SwitchD)
    

    将配置推送到设备

  • 使用字典(dict)来规范交换机的配置参数
  • 接入层与汇聚层在某些配置上不同,而字典中的键是相同的
  • 为了后续调用不同方法配置不同配置,没有相应配置的key都设置了空值,函数在被调用时先判断是否为空值;如果为空,则不进行特定功能的配置;如果不为空,则根据键(key)来调用值(value),进行相应功能的配置;
  • 使用paramiko_SSH_Command(switch)作为一个总方法来调用其余对应各类配置的方法
  • 四台交换机的stp region-configuraion配置相同,并且都需要创建相同的VLAN然后使能STP(默认MSTP模式);除此以外,STP开销都使用华为特有的计算方法;用一个函数common_Configuration封装;
  • 配置access接口时,汇聚层的SwitchA和SwitchB不需要配置,所以调用conf_access_interfaces(remote_connection, access_interfaces, access_vlan) 时,先判断switch对象是否为SwitchC或SwitchD;根据不同Switch指定的接口PVID也不同,比如SwitchC负责偶数vlan下的主机,而SwitchD负责奇数vlan下的主机,通过匹配SwitchC还是SwitchD,来配置不同的PVID;
  • 类似于边缘端口的配置edged_port_bpdu_protection,不受一些特殊条件限制 (如不同Switch配置不同PVID),只是每个交换机上需要配置的接口不同,使能边缘端口命令没有其他参数的参与,就可以先判断需要配置边缘端口的列表是否为空,为空则不进行配置,不为空则for循环调用待配置接口列表,各个接口挨个配置;同理,MSTP根和备用根的配置,也只在SwitchA和SwitchB上进行,通过判断是否为空,就可以避免在SwitchC和SwitchD上配置,然后进一步根据设备字典中的instance相关字段,只在汇聚层设备上调用root_PriSec
  • 查看端口状态和端口保护类型

    SwitchA

    [SwitchA]display stp brief 
     MSTID  Port                        Role  STP State     Protection
       1    Ethernet0/0/1               DESI  FORWARDING      NONE
       1    Ethernet0/0/2               DESI  FORWARDING      ROOT
       2    Ethernet0/0/1               ROOT  FORWARDING      NONE
       2    Ethernet0/0/2               DESI  FORWARDING      ROOT
    
  • 在MSTI1中,SwitchA为根桥,则Ethernet0/0/1和Ethernet0/0/2成为指定端口;
  • 在MSTI2中,SwitchA的Ethernet0/0/1成为指定端口,Ethernet0/0/2成为根端口;
  • SwitchB

    [SwitchB]display stp brief 
     MSTID  Port                        Role  STP State     Protection
       1    Ethernet0/0/1               ROOT  FORWARDING      NONE
       1    Ethernet0/0/2               DESI  FORWARDING      ROOT
       2    Ethernet0/0/1               DESI  FORWARDING      NONE
       2    Ethernet0/0/2               DESI  FORWARDING      ROOT
    
  • 在MSTI2中,SwitchB为根桥,则Ethernet0/0/1和Ethernet0/0/2成为指定端口;
  • 在MSTI1中,SwitchB的Ethernet0/0/2成为指定端口,Ethernet0/0/1成为根端口;
  • SwitchC

    <SwitchC>display stp interface Ethernet 0/0/1 brief 
     MSTID  Port                        Role  STP State     Protection
       1    Ethernet0/0/1               ROOT  FORWARDING      NONE
       2    Ethernet0/0/1               ROOT  FORWARDING      NONE
    <SwitchC>display stp interface Ethernet 0/0/2 brief
     MSTID  Port                        Role  STP State     Protection
       1    Ethernet0/0/2               DESI  FORWARDING      NONE
       2    Ethernet0/0/2               ALTE  DISCARDING      NONE
    
  • 在MSTI1和MSTI2中,Ethernet0/0/1都是根端口;
  • 在MST1中,Ethernet0/0/2为指定端口;在MST2中,Ethernet0/0/1被阻塞;
  • SwitchD

    <SwitchD>display stp interface e0/0/1 brief 
     MSTID  Port                        Role  STP State     Protection
       1    Ethernet0/0/1               ROOT  FORWARDING      NONE
       2    Ethernet0/0/1               ROOT  FORWARDING      NONE
    <SwitchD>display stp interface e0/0/2 brief
     MSTID  Port                        Role  STP State     Protection
       1    Ethernet0/0/2               ALTE  DISCARDING      NONE
       2    Ethernet0/0/2               DESI  FORWARDING      NONE
    
  • 在MSTI1和MSTI2中,Ethernet0/0/1都是根端口;
  • 在MST2中,Ethernet0/0/2为指定端口;在MST1中,Ethernet0/0/1被阻塞;
  • 本文主要在记录自己学习paramiko的过程,旨在提高自己对paramiko的认识和编程能力,规范性不一定强,只是一种探索方式,可能并不能作为一种可靠的学习参考;
  • 文中有很多内容是转译和自己的所思所想,可能存在多处错误,也拜托大家能够指出错误和不足之处。
  • 【1】使用华为模拟器eNSP搭建网络自动化场景

    网络基础回顾

    【1】交换机数据处理流程

    分类:
    开发工具
  •