网络安全实验室系统运维专家程序猿阵线联盟-汇总各类技术干货

交换机配置的自动备份与变更告警

2018-04-13  本文已影响398人  freedomkk_qfeng
image.png

前言

我们做运维的人呢,平时没人在意,出问题的时候才有存在感。所谓日常透明,关键给力嘛。而运维中非常重要的一部分就是备份,它很大程度上决定了我们关键时刻到底能不能给力。

所以今天我们就来聊一聊交换机配置备份的那些事。

配置备份

交换机的配置备份有很多办法,比如

netconf 比较高端一点,但是交换机未必都能支持。而且存在需要开启 netconf 配置的问题,初始工作量较大。

通过 ftp 上传的配置文件最准确,但是不同品牌,不同型号间对应的 ftp 命令差异很大,做起来也是比较麻烦的。

通过命令 show 配置似乎看起来最为简单和直接,命令基本也就 C 家和仿 C 家系列的 show running-config,H 家和他家基友的 display current-configuration ,J 家的 show configuration 这几种。

当然是自动来 show 配置了,手动是不可能 的,这辈子都不可能手动来做的。我们需要自动化的建立远程连接,show 配置,退出,然后把结果存下来。

首先要考虑下走 SSH 还是 TELNET

所以其实很好抉择,SSH 早晚是要全线开启的,而且安全要求严格一些的话, TELNET 应该是要关闭掉。

况且,批量 SSH 的轮子这么多,我们其实根本不需要自己再写一个 SSH 的脚本。比如直接用 multissh 就可以很好的符合我们的需求。

如下所示,使用 multissh ,一条非常简单的命令我们就可以把 SSH 执行 show run 的结果输出到我们指定的目录里,而且速度非常快,15 台交换机同时完成备份只用了 1.3 秒。

[root@wiki-qfeng multissh]# ./multissh -ips "192.168.15.101-192.168.15.115" -cmds "terminal length 0;show run" -u admin -p password -outTxt -f ./conf/      
2018/04/13 21:26:06 Multissh start
2018/04/13 21:26:08 Multissh finished. Process time 1.350904329s. Number of active ip is 15

[root@wiki-qfeng conf]# ls
192.168.15.101.txt  192.168.15.102.txt  192.168.15.103.txt  192.168.15.104.txt  192.168.15.105.txt

[root@wiki-qfeng conf]# cat 192.168.15.101.txt 
sw-1#terminal length 0
sw-1#show run
Building configuration...

Current configuration : 4246 bytes
!
! Last configuration change at 03:25:58 UTC Sun Apr 8 2018 by admin
! NVRAM config last updated at 03:25:57 UTC Sun Apr 8 2018 by admin
!
……
……

我们可以把不同类型交换机,路由器,防火墙的 show 配置命令都提前写好。以文件的方式让 multissh 去读取。如果交换机的密码不一致的话,也可以统统写到一个配置文件里来调用。详细可以参考 用 Go 写一个轻量级的 ssh 批量操作工具

然后把这个命令放到 crontab 定时任务里去就好了。

变更告警

对于一个运维的团队而言,日常运维的操作记录和回溯是非常重要的一件事情。因为绝大部分的故障并非因为软硬件本身,而是来自运维人员的误操作。GitLab 误删过数据库,AWS 误删过系统,携程误操作崩过一整天。实际上只要是人做的操作,误操作就不可能完全避免。

怎么办呢?

一方面是尽量避免误操作的发生,比如更为严格的权限控制和变更审批,堡垒机操作审查等等。另一方面则是在事件发生后能及时的提示和告警,并能快速的溯源导致故障的操作,以便尽快回退。

所以给交换机的配置变更做个告警就也非常重要;额。刚才我们已经备份下了交换机的配置,所以我们只要把每次备份下的配置和上一次备份的配置做一个比对,就能马上得到配置的变更情况。

简单一点,可以直接用 pythondifflib 库来做两个配置文本的比对。difflib 非常简单。如下例,输入两个数组,返回的对比变更情况,并附带上下文(默认三行)

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> for line in unified_diff(s1, s2, fromfile='before.py', tofile='after.py'):
...     sys.stdout.write(line)   
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido

不过对于交换机的配置变更告警而言,我们还需要过滤掉一些东西。比如 ntp clock-period 17179963 类似这样的 nto clock-period 行,隔一段时间他本身就会变一次,而我们并不关心这个东西。因为它的变更而发出的告警就毫无意义,反而可能让真正需要的告警信息淹没在其中。

同理还有类似 Current configuration : 22452 bytesCryptochecksum:ff93c7fc1cb0fd5cf9d113715ce16b62Tue Apr 10 18:08:09.028 UTC 等,这些东西本身就有可能变更而事实上我们并不关心这些。

同时还有一些特殊情况,比如在锐捷的一些版本上,我们就不得不忽略掉 radius-server key 7 xxxxx 这里的变更。因为它丫的这个也自己会变——【交换机】10.X交换机配置radius-server key密文显示后,show run发现密文不同时间会显示不一样

综合这些因素,滤除掉我们不需要参加比较的行之后,我们再对两个配置文件去做 diff 比较。此时的结果就可以发告警了。用 smtplib 来调用 smtp 发送邮件,用 email.mime 来做附件封装的工作,把配置文件作为附件传上去。

完整代码大概如下(有删减)

#!/usr/bin/python
# encoding: utf-8
import difflib
import time
import os
import shutil
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib


conf_path = '/opt/swbackup/conf/'
backup_path = '/opt/swbackup/backup/'
mailto_list=["user1@example.org","user2@example.org","user3@example.org"]
mail_host="smtp.example.org"
mail_user="user@example.org"
mail_pass="mailpasswd"
RuiJie = ["ruijie1.txt","ruijie2.txt","ruijie3.txt"]

####################

def compare(source,target,current,original):
    lines1 = get_lines(source)
    lines2 = get_lines(target)

    result = list(difflib.unified_diff(lines1, lines2, fromfile=current, tofile=original))
    return result

def get_lines(filename):
    f = open(filename)
    lines = f.read().splitlines()
    newlines = []
    split_list = filename.split('/')
    filename = split_list[(len(split_list)-1)]
    for l in lines:
        # 滤除时间行
        if "!Time: " in l:
            continue
        # 滤除空白行
        if (l == "") or (l == "!"):
            continue
        # 滤除配置变更提示行
        if "! Last configuration change at" in l:
            continue
        if "! NVRAM config last updated at" in l:
            continue
        # 滤除配置大小行
        if "Current configuration :" in l:
            continue
        # 滤除 checksum 提示行
        if "Cryptochecksum:" in l:
            continue
        # 滤除 enable 输入行
        if ">enable" in l:
            continue
        # 滤除 Password 输入行
        if "Password:" in l:
            continue
        # 滤除 terminal length 0 输入行
        if "terminal length 0" in l:
            continue
        # 滤除 show run 命令行
        if "show run" in l:
            continue
        # 滤除 display cu 行
        if "display cu" in l:
            continue
        # 滤除 VTY is now available 行
        if ("VTY" in l) and ("is now available" in l):
            continue
        # 滤除时间行
        if (" UTC" in l) and ("config" not in l):
            continue
        # 滤除 ntp clock 行
        if "ntp clock-period" in l:
            continue
        # 滤除 login 时间行
        if "The last login time is" in l:
            continue
        # 滤除 login 时间行
        if "The current login time is" in l:
            continue
        # 对于锐捷,滤除 radius-server key 变更行
        if (filename in RuiJie) and ("radius-server key 7" in l):
            continue
        newlines.append(l + "\n")
    f.close()
    return newlines


def load_conf(path):
    files= os.listdir(path)
    return files


def send_mail(to_list,sub,content,att_path):
    msg = MIMEMultipart()

    msg.attach(MIMEText(content,_subtype='plain',_charset='utf-8'))
    split_list = att_path.split('/')
    filename = split_list[(len(split_list)-1)]
    att = MIMEText(open(att_path, 'rb').read(), 'base64', 'utf-8')
    att["Content-Type"] = 'application/octet-stream'
    att["Content-Disposition"] = 'attachment; filename=' + filename
    msg.attach(att)
    
    msg['Subject'] = sub
    msg['From'] = mail_user
    msg['To'] = ",".join(to_list)
    try:
        s = smtplib.SMTP()
        s.connect(mail_host)
        s.login(mail_user,mail_pass)
        s.sendmail(mail_user, to_list, msg.as_string())
        s.close()
        return True
    except Exception, e:
        print str(e)
        return False

if __name__=='__main__':
    conf_file = load_conf(conf_path)
    backup_file = load_conf(backup_path)

    for f in conf_file:
        now = conf_path + f
        backup = backup_path + f
        if f in backup_file:
            res = compare(backup, now, f + " original",f + " current")
            if len(res) >0:
                content = "".join(res)
                send_mail(mailto_list, "switch_backup " + f, content, now)
        else:
            fl = open(now)
            content = fl.read()
            fl.close()
            send_mail(mailto_list, "switch_backup " + f, content, now)
        shutil.copy(now,  backup)

大抵逻辑就是准备两个目录,一个放备份的配置,一个放 multissh show 出来的配置。

  1. 先分别列出两个目录下的文件,然后拿当前获取的配置文件去备份目录下查。
  2. 如果找不到说明是新备份的交换机,发邮件。配置做正文和附件。
  3. 如果找到了相同的配置文件,那么做 diff 对比。
  4. diff 对比前先过滤掉一些可能会造成误报的配置行。
  5. 如果产生配置变更,发邮件。diff 变更内容做正文,配置做附件。
  6. 所有逻辑执行完以后,将当前 multissh show 得的配置备份至备份目录。等待一下次执行。

很简单的一个脚本。也做个定时任务,把他放在 multissh 的定时任务之后就行。整个交换机配置的备份和变更告警就全自动化完成了。看下效果:


image.png

感觉还行。

参考文献

difflib
【交换机】10.X交换机配置radius-server key密文显示后,show run发现密文不同时间会显示不一样
用 Go 写一个轻量级的 ssh 批量操作工具

以上

转载授权

CC BY-SA

上一篇下一篇

猜你喜欢

热点阅读