CMDB整体项目整理(3)

server端

已经知道client端发送过来的请求是经过加密的request。

  1. 分析请求类型
  2. 解密校验请求数据
  3. 分析获得的数据
  4. 数据入库以及返回相应值

严格来说我写的这个server端并不是一个功能完整的server端。按照最开始画的架构流程图,我这个“server”端只能算一个API端。因为后台部分并没有写。

请求的认证

请求认证信息的构成:

由服务端客户端共同约定好的公钥(随机字符串),和请求发送时间(ctime)共同进行MD5加密后组成。
MD5保证了请求认证不会被反向解码,同时也通过ctime实现了动态加密。

| 哈希处理(公钥+客户端发送请求的数据的时间) | 客户端发送请求的时间 |
|-------------------------------|

请求认证三种情况

(0)、 如果用户发送的请求request不带有认证字符串“HTTP_OPENKEY”,我们就认为他的请求不是来获取或是传递API的。

client_md5_time_key = request.META.get('HTTP_OPENKEY')
if not client_md5_time_key:
    print('这位大哥走错了')
    return HttpResponse('大哥你走错了')

(1)、 我们设置一个超时时限,如果server获取请求的时间和认证信息传递过来的ctime(client端发送请求的时间)超过了这个时限。我们就认为这个请求超时了。
这是为了防止请求中的HTTP_OPENKEY被截获之后被他人利用。

client_md5_key, client_ctime = client_md5_time_key.split('|')
client_ctime = float(client_ctime)
server_time = time.time()
# 分别获取MD5部分和时间部分的,并且获取服务器本地时间

# 第一关
if server_time - client_ctime > 10:
    print('超时的request请求')
    return HttpResponse('【第一关】小伙子,别唬我,太长了')

(2)、上面的情况,如果有人恶意截获请求秘钥,并且修改时间至接近服务器时间,也可以向API发送获取数据。所以还是需要用到不可逆推的md5对请求进行校验。
虽然md5码不可以进行逆推,但是通过客户端发送过来的ctime以及双方约定好的公钥,可以直接在服务端生成一个完全相同的校验码,这样就规避了上面通过靠“猜”生成的时间码的情况。

# 第二关
temp = "%s|%s" % (settings.AUTH_KEY, client_ctime,)
m = hashlib.md5()
m.update(bytes(temp, encoding='utf-8'))
server_md5_key = m.hexdigest()
if server_md5_key != client_md5_key:
    print('')
    return HttpResponse('【第二关】小子,你是不是修改时间了')

(3)、但是还有一个问题就是:如果第三方获取秘钥的速度比服务器接收还快……那样仍然会被第三方获取数据,然后在时间限制内使用同一条认证信息,不断发送数据,同样也可能向API发送错误信息。
所以第三部分就是为了防止重复使用同一条认证信息。这样即便第三方提前获取了密钥,仍然只能把原有数据发送过去,无法利用这段密钥再进行发送。

在全局定义一个api_key_record字典,否则会在每次请求后自动清空。理论上在生产环境,这个字典应该放在一个外部文档或者数据库中。

for k in list(api_key_record.keys()):
    v = api_key_record[k]
    if server_time > v:
        del api_key_record[k]
if client_md5_time_key in api_key_record:
    return HttpResponse('【第三关】有人已经来过了...')
else:
    api_key_record[client_md5_time_key] = client_ctime + 10

以上的几种校验每种不通过都会直接return出函数。经过这些校验之后,才进入请求类型的判断。

附:client端的AUTH函数

def auth():
    """
 API验证
  :return:
 """  import time
    import requests
    import hashlib

    ctime = time.time()
    key = "fsefhsef315lsndvd131vowefnw"
  new_key = "%s|%s" %(key,ctime,)

    m = hashlib.md5()
    m.update(bytes(new_key,encoding='utf-8'))  #里面是字节数据
  md5_key = m.hexdigest()                    #返回值是字符窜类型

  md5_time_key = "%s|%s" %(md5_key,ctime)

    return md5_time_key

request.post请求的处理

解密与反序列化

server_info=decrypt(request.body)
server_info=json.loads(server_info)

获取关键参数

hostname=server_info['basic']['data']['hostname']

# print(hostname)
server_obj = models.Server.objects.filter(hostname=hostname).first()

其实这一步中并不用获取hostname和server_obj,但是考虑到后续调优,减少API对数据库的检索次数,在这里就先放着。

数据入库部分

其实这个本来应该放在request.post请求处理里面的,但是因为内容比较多,也比较典型,所以就拿出来单独说一块。

首先要理清当前数据过来的结构。

basic {'status': True, 'data': {'os_platform': 'Linux', 'os_version': 'ubuntu release 6.6 (Final)\nKernel \r on an \\m', 'hostname': 'c2.com'}}
board {'status': True, 'data': {'manufacturer': 'Parallels Software International Inc.', 'model': 'Parallels Virtual Platform', 'sn': 'Parallels-1A 1B CB 3B 64 66 4B 13 86 B0 86 FF 7E 2B 20 30'}}
cpu {'status': True, 'data': {'cpu_count': 24, 'cpu_physical_count': 2, 'cpu_model': ' Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz'}}
……
nic {'status': True, 'data': {'eth0': {'up': True, 'hwaddr': '00:1c:62:a5:77:7a', 'ipaddrs': '10.211.55.4', 'netmask': '255.255.255.0'}}}

这里隐藏了memory和disk部分的数据,因为实在太长了。在基础数据录入的过程里,我把传过来的字典分为两种。
一种是存在子硬件(subware)的,例如网卡(nic)、内存(memory)、硬盘(disk)。他们都可能有多张网卡,多个内存插槽,多块硬盘。因为存在子硬件这种属性,他们很可能出现,某几个子硬件更新,而其他不变的情况。
另一种是不存在子硬件,仅仅存在属性的,例如CPU,主板这类“要么存在,要么更换,几乎不存在移除情况”的这种硬件信息。
也就是说,在第二种情况中,只要client采集还能获取serverinfo或者关于主机的hostname,这些属性不可能为空(被删除),只可能被修改。

根据以上两种情况,我通过两个典型的例子:“CPU”和“NIC”来进行设计数据分流入库。

通过反射实现数据分流处理:

class Data_post(object):
    def __init__(self,server_info):
        self.info=server_info
        self.data_dict=settings.DATA_DICT
        self.hostname = server_info['basic']['data']['hostname']
        print(self.hostname)

    def data_plugin(self):

        for k,v in self.data_dict.items():
            try:
                module_path,class_name=v.rsplit('.',1)
                m=importlib.import_module(module_path)
                cls=getattr(m,class_name)
                if hasattr(cls,'initial'):
                    obj=cls.initial(self.info,k)
                else:
                    obj=cls(self.info,k)
                first_judge=obj.judge(k)
                one_ret=obj.process(first_judge)
            except Exception as e:
                print('%s info path is wrong, error report:%s'%(k,e))

仍然和client端获取数据的时候一样,本着“从群众中来,到群众中去”的原则,由分函数和插件反射获取的数据,在这里我也一样用设置注册插件的方式来处理数据。
如果没有获取到函数或者在这个位置出现 error report:No module named 'data_post.basic'以外类型的错误。利用traceback模块中的traceback.format_exc()替换e进行调试。

没有子硬件(subware)的数据入库

定义CPU类必须传入的两个参数:serverinfo(采集来的数据)、k(采集获取的当前数据类型)

from repository import models

class Cpu(object):
    def __init__(self,server_info,k):
        self.info=server_info
        self.hostname=server_info['basic']['data']['hostname']
        self.obj=models.Server.objects.filter(hostname=self.hostname).first()

判断是否有采集错误的信息

    def judge(self,k):
        if not self.info[k]['status']:
            models.ErrorLog.objects.create(content=self.info[k]['data'],asset_obj=self.obj.asset,title='【%s】 %s 采集错误信息'%(self.hostname,k))
        # 如果没有采集错误,因为CPU这类信息没有subware,可以用两个字典完成新老数据的比对。
        else:
            # cpu这类数据并没有subware这个选项
            self.new_hd_dict=self.info[k]['data']
            self.old_hd_dict=models.Server.objects.filter(hostname=self.hostname).values(
                'cpu_count','cpu_physical_count','cpu_model'
            ).first()
            # CPU的信息来自于Server,需要从server端获取对应的信息

            record_dict={}
            for k1,v1 in self.new_hd_dict.items():
                if self.old_hd_dict[k1] !=v1:
                    record_dict[k1]=v1
            # 将对比之后的数据存放在record里面
            print(record_dict)
            # 为了方便后端数据记录,还要保存一个后端原数据
            record_dict['old_dict']=self.old_hd_dict
            return record_dict

    def process(self,record_dict):

        # ############### 更新 ################
        old_dict = record_dict.pop('old_dict')
        # 这里只有一个更新功能,拿一个字典把olddict拿到,并且恢复record_dict
        if record_dict:
            models.Server.objects.filter(hostname=self.hostname).update(**record_dict)
            models.AssetRecord.objects.create(asset_obj=self.obj.asset,content="更新CPU信息:old:%s ---- new:%s"%(old_dict,record_dict))

有子硬件的数据入库

from repository import models

class Nic(object):
    def __init__(self,server_info,k):
        self.info=server_info
        self.hostname=server_info['basic']['data']['hostname']

        self.obj=models.Server.objects.filter(hostname=self.hostname).first()

    # @classmethod
 #     def initial(cls):
 #        return cls()

  def judge(self,k):
        if not self.info[k]['status']:
            models.ErrorLog.objects.create(content=self.info[k]['data'],asset_obj=self.obj.asset,title='【%s】 %s 采集错误信息'%(self.hostname,k))

        else:
            self.new_hd_dict=self.info[k]['data']

            self.old_hd_list=models.NIC.objects.filter(server_obj=self.obj)

            self.new_subware_list=list(self.new_hd_dict.keys())
            self.old_subware_list=[]
            for item in self.old_hd_list:
                self.old_subware_list.append(item.name)

            update_list=set(self.new_subware_list).intersection(self.old_subware_list)
            create_list=set(self.new_subware_list).difference(self.old_subware_list)
            del_list=set(self.old_subware_list).difference(self.new_subware_list)

            record_dict={'updata_list':update_list,'create_list':create_list,'del_list':del_list}
            return record_dict

增删改

def process(self,record_dict):
    # ############### 删除 ################
    if record_dict['del_list']:
        models.NIC.objects.filter(server_obj=self.obj,name__in=record_dict['del_list']).delete()
        models.AssetRecord.objects.create(asset_obj=self.obj,content="移除网卡:%s"%('、'.join(record_dict['del_list'])))

    # ############### 添加 ################
    if record_dict['create_list']:
        record_list = []
        for eth in record_dict['create_list']:
            nic_dict = self.new_hd_dict[eth]
            nic_dict['name'] = eth
            nic_dict['server_obj'] = self.obj

            models.NIC.objects.create(**nic_dict)

            temp = "新增网卡:状态{up},硬件码{hwaddr},IP:{ipaddrs},掩码:{netmask}".format(**nic_dict)

            record_list.append(temp)
        if record_list:
            content = ";".join(record_list)
            models.AssetRecord.objects.create(asset_obj=self.obj.asset, content=content)

    # ############### 更新 ################
    if record_dict['updata_list']:
        record_list = []
        row_map = {'up': '状态', 'hwaddr': '硬件码', 'ipaddrs': 'IP', 'netmask': '掩码'}
        for name in record_dict['updata_list']:
            new_nic_row = self.new_hd_dict[name]
            old_nic_row = models.NIC.objects.filter(name=name, server_obj=self.obj).first()

            for k, v in new_nic_row.items():

                value = getattr(old_nic_row, k)
                if v != value:
                    record_list.append("IP%s,%s由%s变更为%s" % (name, row_map[k], value, v,))
                    setattr(old_nic_row, k, v)
            old_nic_row.save()
        if record_list:
            content = ";".join(record_list)
            models.AssetRecord.objects.create(asset_obj=self.obj.asset, content=content)</pre>
posted @ 2017-10-25 15:03  sc0T7_ly  阅读(309)  评论(0编辑  收藏  举报