• 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)
    Kernel 
     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>
    
  • 相关阅读:
    DB2数据库BACKUP PENDING状态(修改日志模式导致)(转)
    JAVA调用WebService实例
    eclipse启动报JVM terminated. Exit code=-1的解决方法
    JAVA使用Dom4j组装、解析XML
    任务调度IBM Tivoli Workload Scheduler(TWS)
    Java遍历Map数据的几种方式
    谈谈我对Log4j2以外的感想
    ESQL中添加JMS参数
    node.js-node-inspector调试
    前端-浏览器内核
  • 原文地址:https://www.cnblogs.com/scott-lv/p/7728913.html
Copyright © 2020-2023  润新知