def post(self): ret = self.check_privilege(TP_PRIVILEGE_ASSET_CREATE | TP_PRIVILEGE_ASSET_DELETE | TP_PRIVILEGE_ASSET_GROUP) if ret != TPE_OK: return args = self.get_argument('args', None) if args is None: return self.write_json(TPE_PARAM) try: args = json.loads(args) except: return self.write_json(TPE_JSON_FORMAT) try: args['id'] = int(args['id']) args['os_type'] = int(args['os_type']) args['ip'] = args['ip'].strip() args['router_ip'] = args['router_ip'] args['router_port'] = int(args['router_port']) args['name'] = args['name'].strip() args['cid'] = args['cid'].strip() args['desc'] = args['desc'].strip() except: log.e('\n') return self.write_json(TPE_PARAM) if len(args['ip']) == 0: return self.write_json(TPE_PARAM) if args['id'] == -1: err, info = host.add_host(self, args) else: err = host.update_host(self, args) info = {} self.write_json(err, data=info)
def post(self): """ csv导入规则: 每一行的数据格式: 主机IP,操作系统类型,名称,路由IP,路由端口,资产编号,账号,协议类型,协议端口,认证类型,密码或私钥,账号提示,密码提示,分组,描述 在导入时: 0. 以“#”作为行注释。 1. 首先必须是主机数据,随后可以跟多个账号数据(直到遇到下一个主机数据行之前,账号会与上一个主机关联)。 2. 一个主机或账号属于多个组,可以用“|”将组分隔,如果某个组并不存在,则会创建这个组。 3. 空行跳过,数据格式不正确的跳过。 """ ret = dict() ret['code'] = TPE_OK ret['message'] = '' rv = self.check_privilege( TP_PRIVILEGE_ASSET_CREATE | TP_PRIVILEGE_ASSET_GROUP | TP_PRIVILEGE_USER_CREATE | TP_PRIVILEGE_USER_GROUP, need_process=False) if rv != TPE_OK: ret['code'] = rv if rv == TPE_NEED_LOGIN: ret['message'] = '需要登录!' elif rv == TPE_PRIVILEGE: ret['message'] = '权限不足!' else: ret['message'] = '未知错误!' return self.write(json.dumps(ret).encode('utf8')) # success = list() failed = list() group_failed = list() csv_filename = '' try: upload_path = os.path.join(tp_cfg().data_path, 'tmp') # 文件的暂存路径 if not os.path.exists(upload_path): os.mkdir(upload_path) file_metas = self.request.files[ 'csvfile'] # 提取表单中‘name’为‘file’的文件元数据 for meta in file_metas: now = time.localtime(time.time()) tmp_name = 'upload-{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}.csv'.format( now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec) csv_filename = os.path.join(upload_path, tmp_name) with open(csv_filename, 'wb') as f: f.write(meta['body']) # file encode maybe utf8 or gbk... check it out. file_encode = None with codecs.open(csv_filename, 'r', encoding='gbk') as f: try: f.readlines() file_encode = 'gbk' except: pass if file_encode is None: log.v('file `{}` is not gbk, try utf8\n'.format(csv_filename)) with codecs.open(csv_filename, 'r', encoding='utf_8_sig') as f: try: f.readlines() file_encode = 'utf_8_sig' except: pass if file_encode is None: os.remove(csv_filename) log.e( 'file `{}` unknown encode, neither GBK nor UTF8.\n'.format( csv_filename)) ret['code'] = TPE_FAILED ret['message'] = '文件无法解码:不是GBK编码或者UTF8编码!' return self.write(json.dumps(ret).encode('utf8')) host_groups = dict() # 解析出来的主机分组名称 acc_groups = dict() # 解析出来的账号分组名称 hosts = dict() # 解析出来的主机列表(其中包含对应的账号列表,是一个一对多的关系) # 解析csv文件 with open(csv_filename, encoding=file_encode) as f: all_acc = [] # 用于检查是否有重复的账号被添加 csv_reader = csv.reader(f) line = 0 last_ip = None for csv_recorder in csv_reader: line += 1 # 跳过空行和注释 if len(csv_recorder) == 0 or csv_recorder[0].strip( ).startswith('#'): continue # 格式错误则记录在案,然后继续 if len(csv_recorder) != 15: failed.append({'line': line, 'error': '格式错误,字段数量不匹配。'}) continue # check _ip = csv_recorder[self.IDX_IP].strip() _username = csv_recorder[self.IDX_USERNAME].strip() if len(_ip) == 0 and len(_username) == 0: failed.append({ 'line': line, 'error': '格式错误,需要IP或者账号。' }) continue # 公用字段 _group = list() for g in csv_recorder[self.IDX_GROUP].split('|'): g = g.strip() if len(g) > 0 and g not in _group: _group.append(g) _desc = csv_recorder[self.IDX_DESC].strip().replace( "\\n", "\n") if len(_ip) > 0: last_ip = None try: ipaddress.ip_address(_ip) except: failed.append({ 'line': line, 'error': '格式错误,错误的主机IP地址。' }) continue if _ip in hosts: failed.append({'line': line, 'error': '主机重复。'}) continue # 检查主机数据 _host_os = csv_recorder[self.IDX_OS].strip().upper() if _host_os == 'WIN' or _host_os == 'WINDOWS': _host_os = TP_OS_TYPE_WINDOWS elif _host_os == 'LINUX': _host_os = TP_OS_TYPE_LINUX else: failed.append({ 'line': line, 'error': '格式错误,错误的主机操作系统类型。' }) continue _router_ip = csv_recorder[self.IDX_ROUTER_IP].strip() _router_port = csv_recorder[ self.IDX_ROUTER_PORT].strip() if not ( (len(_router_ip) == 0 and len(_router_port) == 0) or (len(_router_ip) > 0 and len(_router_port) > 0)): failed.append({ 'line': line, 'error': '格式错误,错误的路由IP及端口的组合。' }) continue if len(_router_ip) > 0: try: ipaddress.ip_address(_ip) except: failed.append({ 'line': line, 'error': '格式错误,错误的路由IP地址。' }) continue try: _router_port = int(_router_port) except: failed.append({ 'line': line, 'error': '格式错误,错误的路由端口。' }) continue if _router_port < 0 or _router_port > 65535: failed.append({ 'line': line, 'error': '格式错误,错误的路由端口。' }) continue else: _router_port = 0 _host = dict() _host['_line'] = line _host['ip'] = _ip _host['os'] = _host_os _host['name'] = csv_recorder[self.IDX_NAME].strip() _host['router_ip'] = _router_ip _host['router_port'] = _router_port _host['cid'] = csv_recorder[self.IDX_IDC].strip() _host['group'] = _group _host['desc'] = _desc _host['acc'] = list() hosts[_ip] = _host last_ip = _ip else: # account if last_ip is None: failed.append({ 'line': line, 'error': '无对应主机信息,跳过。' }) continue _protocol = csv_recorder[ self.IDX_PROTOCOL].strip().upper() if _protocol == 'RDP': _protocol = TP_PROTOCOL_TYPE_RDP elif _protocol == 'SSH': _protocol = TP_PROTOCOL_TYPE_SSH elif _protocol == 'TELNET': _protocol = TP_PROTOCOL_TYPE_TELNET else: failed.append({ 'line': line, 'error': '格式错误,错误的远程协议类型。' }) continue _protocol_port = csv_recorder[ self.IDX_PROTOCOL_PORT].strip() if hosts[last_ip]['router_port'] == 0: if len(_protocol_port) == 0: failed.append({ 'line': line, 'error': '格式错误,需要填写远程协议端口。' }) continue try: _protocol_port = int(_protocol_port) except: failed.append({ 'line': line, 'error': '格式错误,远程协议端口应该是数字。' }) continue else: _protocol_port = 0 _auth = csv_recorder[self.IDX_AUTH].strip().upper() if _auth == 'NO': _auth = TP_AUTH_TYPE_NONE elif _auth == 'PW': _auth = TP_AUTH_TYPE_PASSWORD elif _auth == 'KEY': _auth = TP_AUTH_TYPE_PRIVATE_KEY else: failed.append({ 'line': line, 'error': '格式错误,错误的认证类型。' }) continue _secret = csv_recorder[self.IDX_SECRET].strip() if _auth != TP_AUTH_TYPE_NONE and len(_secret) == 0: failed.append({ 'line': line, 'error': '格式错误,密码或私钥必须填写。' }) continue _username_prompt = csv_recorder[ self.IDX_USERNAME_PROMPT].strip() _password_prompt = csv_recorder[ self.IDX_PASSWORD_PROMPT].strip() if _protocol != TP_PROTOCOL_TYPE_TELNET: _username_prompt = '' _password_prompt = '' _acc_info = '{}-{}-{}'.format(last_ip, _username, _auth) if _acc_info in all_acc: failed.append({'line': line, 'error': '账号重复。'}) continue all_acc.append(_acc_info) _acc = dict() _acc['_line'] = line _acc['username'] = _username _acc['protocol_type'] = _protocol _acc['protocol_port'] = _protocol_port _acc['auth_type'] = _auth _acc['secret'] = _secret _acc['username_prompt'] = _username_prompt _acc['password_prompt'] = _password_prompt _acc['group'] = _group _acc['desc'] = _desc hosts[last_ip]['acc'].append(_acc) if os.path.exists(csv_filename): os.remove(csv_filename) # 如果解析过程中发生问题,则不再继续 if len(failed) > 0: ret['code'] = TPE_FAILED ret['message'] = '文件解析错误,请修改文件后重新上传!' ret['data'] = failed return self.write(json.dumps(ret).encode('utf8')) if len(hosts) == 0: ret['code'] = TPE_FAILED ret['message'] = '上传的 csv 文件中没有可用于导入的数据!' ret['data'] = failed return self.write(json.dumps(ret).encode('utf8')) # 对密码或私钥进行加密(需要连接到核心服务) for ip in hosts: for i in range(len(hosts[ip]['acc'])): if len(hosts[ip]['acc'][i]['secret']) == 0: continue code, ret_data = yield core_service_async_enc( hosts[ip]['acc'][i]['secret']) if code != TPE_OK: ret['code'] = code ret['message'] = '无法对密码或私钥加密。' if code == TPE_NO_CORE_SERVER: ret['message'] += '可能核心服务尚未启动。' ret['data'] = failed return self.write(json.dumps(ret).encode('utf8')) hosts[ip]['acc'][i]['secret'] = ret_data # 分析要创建的主机分组和账号分组 for ip in hosts: for g in hosts[ip]['group']: if g not in host_groups: host_groups[g] = 0 for i in range(len(hosts[ip]['acc'])): for g in hosts[ip]['acc'][i]['group']: if g not in acc_groups: acc_groups[g] = 0 # 已经有了用户组列表,查询组数据库,有则更新用户组列表中组对应的id,无则创建组 if len(host_groups) > 0: err = group.make_groups(self, TP_GROUP_HOST, host_groups, group_failed) if len(group_failed) > 0: ret['code'] = TPE_FAILED ret['message'] += '无法创建主机组 {}。'.format( ','.join(group_failed)) return self.write(json.dumps(ret).encode('utf8')) if len(acc_groups) > 0: err = group.make_groups(self, TP_GROUP_ACCOUNT, acc_groups, group_failed) if len(group_failed) > 0: ret['code'] = TPE_FAILED ret['message'] += '无法创建账号组 {}。'.format( ','.join(group_failed)) return self.write(json.dumps(ret).encode('utf8')) # 创建主机和账号 for ip in hosts: # router_addr = '' # if hosts[ip]['router_port'] > 0: # router_addr = '{}:{}'.format(hosts[ip]['router_ip'], hosts[ip]['router_port']) args = dict() args['ip'] = ip args['router_ip'] = hosts[ip]['router_ip'] args['router_port'] = hosts[ip]['router_port'] args['os_type'] = hosts[ip]['os'] args['name'] = hosts[ip]['name'] args['cid'] = hosts[ip]['cid'] args['desc'] = hosts[ip]['desc'] err, host_id = host.add_host(self, args) if err != TPE_OK: hosts[ip]['host_id'] = 0 if err == TPE_EXISTS: failed.append({ 'line': hosts[ip]['_line'], 'error': '增加主机{}失败,此主机已经存在。'.format(ip) }) else: failed.append({ 'line': hosts[ip]['_line'], 'error': '增加主机{}失败,数据库操作失败。'.format(ip) }) continue hosts[ip]['host_id'] = host_id for i in range(len(hosts[ip]['acc'])): args = dict() args['host_ip'] = ip args['router_ip'] = hosts[ip]['router_ip'] args['router_port'] = hosts[ip]['router_port'] # args['host_router_addr'] = router_addr args['host_id'] = host_id args['protocol_type'] = hosts[ip]['acc'][i][ 'protocol_type'] args['protocol_port'] = hosts[ip]['acc'][i][ 'protocol_port'] args['auth_type'] = hosts[ip]['acc'][i]['auth_type'] args['username'] = hosts[ip]['acc'][i]['username'] args['password'] = '' args['pri_key'] = '' if args['auth_type'] == TP_AUTH_TYPE_PASSWORD: args['password'] = hosts[ip]['acc'][i]['secret'] elif args['auth_type'] == TP_AUTH_TYPE_PRIVATE_KEY: args['pri_key'] = hosts[ip]['acc'][i]['secret'] args['username_prompt'] = _acc['username_prompt'] args['password_prompt'] = _acc['password_prompt'] err, acc_id = account.add_account(self, host_id, args) if err == TPE_EXISTS: failed.append({ 'line': hosts[ip]['acc']['_line'], 'error': '增加账号{}@{}失败,账号已经存在。'.format(args['username'], ip) }) continue elif err != TPE_OK: failed.append({ 'line': hosts[ip]['acc']['_line'], 'error': '增加账号{}@{}失败,数据库操作失败。'.format( args['username'], ip) }) hosts[ip]['acc'][i]['acc_id'] = acc_id # 没毛病,那就创建组和成员的映射关系 for ip in hosts: if hosts[ip]['host_id'] == 0: continue gm = list() for hg in hosts[ip]['group']: for g in host_groups: if host_groups[g] == 0 or hg != g: continue gm.append({ 'type': 2, 'gid': host_groups[g], 'mid': hosts[ip]['host_id'] }) group.make_group_map(TP_GROUP_HOST, gm) for i in range(len(hosts[ip]['acc'])): if hosts[ip]['acc'][i]['acc_id'] == 0: continue gm = list() for ag in hosts[ip]['acc'][i]['group']: for g in acc_groups: if acc_groups[g] == 0 or ag != g: continue gm.append({ 'type': 3, 'gid': acc_groups[g], 'mid': hosts[ip]['acc'][i]['acc_id'] }) group.make_group_map(TP_GROUP_ACCOUNT, gm) # ret['code'] = TPE_FAILED # ret['message'] = '-----------!' # ret['data'] = failed # return self.write(json.dumps(ret).encode('utf8')) # # # 对创建成功的用户,在用户组映射表中设定其对应关系 # gm = list() # for u in user_list: # if u['_id'] == 0: # continue # for ug in u['_group']: # for g in group_list: # if group_list[g] == 0 or ug != g: # continue # gm.append({'type': 1, 'gid': group_list[g], 'mid': u['_id']}) # # user.make_user_group_map(gm) # if len(failed) == 0: ret['code'] = TPE_OK # ret['message'] = '所有 {} 个用户账号均已导入!'.format(len(success)) ret['message'] = '导入操作已成功结束!' return self.write(json.dumps(ret).encode('utf8')) else: ret['code'] = TPE_FAILED # if len(success) > 0: # ret['message'] = '{} 个用户账号导入成功,'.format(len(success)) ret['message'] = '部分主机和账号导入成功,' ret['message'] += '{} 个主机和账号未能导入!'.format(len(failed)) ret['data'] = failed return self.write(json.dumps(ret).encode('utf8')) except: log.e('got exception when import host and account.\n') ret['code'] = TPE_FAILED # if len(success) > 0: # ret['message'] += '{} 个用户账号导入后发生异常!'.format(len(success)) # else: # ret['message'] = '发生异常!' ret['message'] = '发生异常!' if len(failed) > 0: ret['data'] = failed return self.write(json.dumps(ret).encode('utf8'))