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())
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")
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)
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())
ssh_client.connect(hostname = ip_address, username = username, password = password)
Paramiko官方文档给出的paramiko.client.SSHClient
类的描述为:
与SSH服务器建立SSH会话的高级表示,该类封装了Transport,Channel和SFTPClient类来处理好认证和打开通道(channel)。
SSHClient
的典型使用方式为:
client = SSHClient()
client.load_system_host_keys()
client.connect('ssh.example.com')
使用SSHClient()
创建SSH客户端,上传脚本的一端作为服务端,然后调用load_system_host_keys(filename = None)
读取主机密钥,参数filename
默认为None
,则会尝试从用户的本地 "known hosts"文件中读取密钥;再调用connect()
与hostname
为ssh.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_keys
或load_host_keys
方法了
remote_connection = ssh_client.invoke_shell()
remote_connection.send("system\n")
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)
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_client
;close
直接关闭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"
如果都能成功登录,则说明Ubuntu与交换机连通性良好,并且可以进行远程登录;
编写脚本并上传到设备
配置内容总览
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())
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):
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)
print("Configuring stp pathcost-standard legacy ...")
remote_connection.send("stp pathcost-standard legacy\n")
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")
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):
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.")
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】交换机数据处理流程