前言:
最近研究zabbix告警,網上看了帖子有各式各樣姿勢:電話語音告警,郵件告警,短信告警,微信公衆號告警等等等..姿勢五花八門,真是糾結。
電話語音告警,短信告警首先pass 前者花錢,後者通過設置139郵箱,就可以實現僞短信告警效果。
剩下郵件告警與微信公衆號告警。郵件告警已經在部署的時候配置完畢,剩下這個微信公衆號告警,查一下帖子,申請各種麻煩。那麼有沒有基於微信個人賬號的告警呢?想到這個點,馬上github一番。
搜索到一些優秀的開源代碼:https://github.com/0x5e/wechat-deleted-friends
及封包:https://github.com/xiangzhai/qwx/blob/master/doc/protocol.md
經過一番思考,大致思路如下:
微信WEB保持活動狀態,通過不斷使用zabbix api抓取故障告警,入庫後微信發送告警給相關人員。
一 需求實現具體思路
實現該需求是得有多個線程同時進行的
微信心跳,微信WEB版保持活動狀態。
ZABBIX API ,不斷請求zabbix告警,發現告警後,判斷後入庫。
使用數據庫不斷查詢告警,如果發現符合條件告警則發送告警給相關人員。
二 部分代碼及註釋
第一部分:wechat
# coding=utf-8 #僞裝請求頭 headers = {'User-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36'} myRequests = requests.Session() myRequests.headers.update(headers) DEBUG = False #微信類 class WeChat(object): def __init__(self): self.uuid = '' self.base_uri = '' self.push_uri = '' self.redirect_uri = '' self.BaseRequest = {} self.skey = '' self.wxuin = '' self.wxsid = '' self.skey = '' self.deviceId = 'e' + repr(random())[2:17] #隨機生成15位機器碼 self.pass_ticket = '' self.MemberList =[] self.ContactList = [] self.AlarmFriends =[] self.Intervals = '' self.xintiao = '' #獲取UUID def Get_UUID(self): url = 'https://login.weixin.qq.com/jslogin' params = { 'appid': 'wx782c26e4c19acffb', 'fun': 'new', 'lang': 'zh_CN', '_': int(time.time()), } r = myRequests.get(url=url, params=params) r.encoding = 'utf-8' data = r.text # data返回,code=200爲狀態.uuid="IZTW06WnSg=="爲uuid # window.QRLogin.code = 200; window.QRLogin.uuid = "IZtWO6WnSg=="; # 正則匹配:匹配出狀態碼 以及UUID regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' PM = re.search(regx, data) code = PM.group(1) if code == '200': self.uuid = PM.group(2) return True return False # windows下直接打開二維碼圖 def _openWinQRCodeImg(self): url = 'https://login.weixin.qq.com/qrcode/' + self.uuid params = { 't': 'webwx', '_': int(time.time()) } r = myRequests.get(url=url, params=params) f = open(QRImagePath, 'wb') f.write(r.content) f.close() time.sleep(1) os.startfile(QRImagePath) # Linux下的二維碼處理 def _printQR(self, mat): for i in mat: BLACK = '\033[40m \033[0m' WHITE = '\033[47m \033[0m' print (''.join([BLACK if j else WHITE for j in i])) def _str2qr(self, str): qr = qrcode.QRCode() qr.border = 1 qr.add_data(str) mat = qr.get_matrix() self._printQR(mat) # qr.print_tty() or qr.print_ascii() # 判斷操作系統,選擇打開二維碼掃描方式 def genQRCode(self): if sys.platform.startswith('win'): self._openWinQRCodeImg() else: self._str2qr('https://login.weixin.qq.com/l/' + self.uuid) #等待登陸 def WaitForLogin(self, tip=1): time.sleep(tip) url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % ( tip, self.uuid, int(time.time())) r = myRequests.get(url=url) r.encoding = 'utf-8' data = r.text # data返回: # window.code = 201; #判斷返回碼 regx = r'window.code=(\d+);' pm = re.search(regx, data) code = pm.group(1) if code == '201': # 已掃描 print('[*]成功掃描,請在手機上點擊確認以登錄') elif code == '200': # 已登錄 print('[.]正在登錄...') regx = r'window.redirect_uri="(\S+?)";' pm = re.search(regx, data) self.redirect_uri = pm.group(1) + '&fun=new' base_uri = self.redirect_uri[:self.redirect_uri.rfind('/')] # push_uri與base_uri對應關係(排名分先後) services = [ ('wx2.qq.com', 'webpush2.weixin.qq.com'), ('qq.com', 'webpush.weixin.qq.com'), ('web1.wechat.com', 'webpush1.wechat.com'), ('web2.wechat.com', 'webpush2.wechat.com'), ('wechat.com', 'webpush.wechat.com'), ('web1.wechatapp.com', 'webpush1.wechatapp.com'), ] # self.push_uri = self.base_uri self.push_uri = base_uri for (searchUrl, pushUrl) in services: if base_uri.find(searchUrl) >= 0: self.push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl self.base_uri = 'https://%s/cgi-bin/mmwebwx-bin' % searchUrl break elif code == '408': # 超時 pass # elif code == '400' or code == '500': return code #登陸 def login(self): r = myRequests.get(url=self.redirect_uri) r.encoding = 'utf-8' data = r.text # print (data) # data返回 # < ret > 0 < / ret > < message > OK < / message > # < skey >XXXX < skey > # < wxsid > XXXX < / wxsid > # < wxuin > XXXX < / wxuin > # < pass_ticket > XXXX < / pass_ticket > # < isgrayscale > 1 < / isgrayscale > #解析XML文件 doc = xml.dom.minidom.parseString(data) root = doc.documentElement for node in root.childNodes: if node.nodeName == 'skey': self.skey = node.childNodes[0].data elif node.nodeName == 'wxsid': self.wxsid = node.childNodes[0].data elif node.nodeName == 'wxuin': self. wxuin = node.childNodes[0].data elif node.nodeName == 'pass_ticket': self.pass_ticket = node.childNodes[0].data # print('skey: %s, wxsid: %s, wxuin: %s, pass_ticket: %s' % (skey, wxsid,wxuin, pass_ticket)) if not all((self.skey, self.wxsid, self.wxuin, self.pass_ticket)): return False self.BaseRequest = { 'Uin': int(self.wxuin), 'Sid': self.wxsid, 'Skey': self.skey, 'DeviceID': self.deviceId, } # print (self.push_uri) return True def webwxinit(self): url = ( self.base_uri +'/webwxinit?pass_ticket=%s&skey=%s&r=%s' \ % (self.pass_ticket, self.skey, int(time.time()))) params = {'BaseRequest': self.BaseRequest} headers = {'content-type': 'application/json; charset=UTF-8'} r = myRequests.post(url=url, data=json.dumps(params), headers=headers) r.encoding = 'utf-8' data = r.json() self.SyncKey = data['SyncKey'] self.User = data['User'] if False: f = open(os.path.join(os.getcwd(), 'webwxinit.json'), 'wb') f.write(r.content) f.close() self.synckey = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val']) for keyVal in self.SyncKey['List']]) state = self.responseState('webwxinit', data['BaseResponse']) return state def webwxstatusnotify(self): url = self.base_uri + \ '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (self.pass_ticket) params = { 'BaseRequest': self.BaseRequest, "Code": 3, "FromUserName": self.User['UserName'], "ToUserName": self.User['UserName'], "ClientMsgId": int(time.time()) } r = myRequests.post(url=url, params=json.dumps(params)) data = r.json() state = self.responseState('WexinStatusNoTify',data['BaseResponse']) #return data['BaseResponse']['Ret'] == 0 return state #獲取好友列表 def webwxgetcontact(self): url = (self.base_uri +'/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (\ self.pass_ticket, self.skey, int(time.time()))) headers = {'content-type': 'application/json; charset=UTF-8'} r = myRequests.post(url=url, headers=headers) r.encoding = 'utf-8' data = r.json() if False: f = open(os.path.join(os.getcwd(), 'webwxgetcontact.json'), 'wb') f.write(r.content) f.close() self.MemberList = data['MemberList'] SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage",\ "qqsync","floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp",\ "blogapp", "facebookapp", "masssendapp","meishiapp", "feedsapp", "voip",\ "blogappweixin", "weixin", "brandsessionholder", "weixinreminder",\ "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts",\ "notification_messages", "wxitil", "userexperience_alarm"] #將列表中特殊賬號刪除 for i in range(len(self.MemberList) - 1, -1, -1): Member = self.MemberList[i] if Member['VerifyFlag'] & 8 != 0: # 公衆號/服務號 self.MemberList.remove(Member) elif Member['UserName'] in SpecialUsers: # 特殊賬號 self.MemberList.remove(Member) elif Member['UserName'].find('@@') != -1: # 羣聊 self.MemberList.remove(Member) elif Member['UserName'] == self.User: # 自己 self.MemberList.remove(Member) self.ContactList = self.MemberList return True #發送信息 def webwxsendmsg(self, word, to='filehelper'): url = self.base_uri + \ '/webwxsendmsg?pass_ticket=%s' % (self.pass_ticket) clientMsgId = str(int(time.time() * 1000)) + \ str(random())[:5].replace('.', '') params = { 'BaseRequest':{ "Uin": int(self.wxuin), "Sid": self.wxsid, "Skey":self.skey, "DeviceID": self.deviceId, }, 'Scene': 0, 'Msg':{ "Type": 1, "Content": self._transcoding(word), "FromUserName": self.User['UserName'], "ToUserName": to, "LocalID": clientMsgId, "ClientMsgId": clientMsgId, } } headers = {'content-type': 'application/json; charset=UTF-8'} data = json.dumps(params, ensure_ascii=False).encode('utf8') r =myRequests.post(url,data=data,headers=headers) dic = r.json() state = self.responseState('SendMsg', dic['BaseResponse']) print (params) return state # print (params) def Wx_Views(self): print ('[.]正在獲取好友列表..') list = self.ContactList Alarmlist=[] # list = json.dump(List,ensure_ascii=False) for i in range(0,len(list)): if list: list[i]['id'] = i Name = self._untostr(list[i]['NickName']) Rname = self._untostr(list[i]['RemarkName']) Id = i #print (list[i]) print ('\t %d \t姓名:%s \t 備註:%s' %(Id,Name,Rname)) else: print ('[!]獲取失敗!') exit() while True: try: iNput = raw_input("[.]請設置告警對象ID,使用空格隔開\n") Alist = iNput.split(' ') except: print ("[!]輸入錯誤!") try: for i in range(0,len(Alist)): if Alist: for j in range(0, len(list)): if int(Alist[i]) == list[j]['id']: print ('[*]你設置的對象是:%s' % self._untostr(list[j]['NickName'])) Alarmlist.append(list[j]['UserName']) self.AlarmFriends.append(list[j]['UserName']) else: pass except: continue if self.AlarmFriends: Input = raw_input ("[!]確認設置(y/n)") if Input == 'y': self.AlarmFriends = Alarmlist break elif Input == 'n': Alarmlist = [] self.AlarmFriends = [] pass else: Alarmlist = [] self.AlarmFriends = [] print ("[!]輸入錯誤") else: print ("[!]檢測不到有效輸入,請重試") print (self.AlarmFriends) def Wx_heartBeatLoop(self): while True: selector = self.syncCheck() if selector != '0': self.webwxsync() time.sleep(int(self.xintiao)) print ("[*]Wechat心跳正常..") def run(self): while True: time.sleep(5) SleepTime = int(self.Intervals) print("[*]告警檢測心跳..") Time = int(time.time()) LastTime = Time - int(SleepTime) Select_sql = "SELECT * FROM wechat_sendmsg WHERE TIME BETWEEN %d and %d" % (LastTime,Time) data = db.select(Select_sql) # print (data) if data: for i in data: print (data ) triggerTime = self._untostr(i[0]) Hostname = self._untostr(i[2]) HostIP = self._untostr(i[3]) Description = self._untostr(i[4]) level = self._untostr(i[5]) msg = """ [!]發現告警 告警服務器:%s 告警時間:%s 告警IP:%s 告警項:%s 告警級別:%s """ %(Hostname,triggerTime,HostIP,Description,level) print (msg) for j in range(0,len(self.AlarmFriends)): self.webwxsendmsg(msg,self.AlarmFriends[j]) # print (j) # print (self.AlarmFriends) else: pass
第二部分:zabbix API
from ZabbixTriggerDb import SQLiteDB myRequests = requests.Session() db = SQLiteDB class Zabbix(object): def __init__(self): self.Holist = [] self.Zabbix_Address = '' self.Zabbix_Username='' self.Time = time.strftime('%Y-%m-%d %H:%M') self.Passwd = '' self.z_Intervals = '' self.w_Intervals = '' self.sleeptime = '' self.Trigger= [] self.LastTrigger = [] self.WxTriggerList = [] #獲取zabbix api token def get_auth(self): url = '%s/api_jsonrpc.php' % self.Zabbix_Address params = json.dumps({ "jsonrpc": "2.0", "method": "user.login", "params": { "user": self.Zabbix_Username, "password": self.Passwd }, "id": 0 }) headers = {'content-type': 'application/json; charset=UTF-8'} r = myRequests.post(url=url, data=params, headers=headers) r.encoding = 'utf-8' data = r.json() return data['result'] #獲取zabbix監控主機列表 def get_host(self): url = '%s/api_jsonrpc.php' % self.Zabbix_Address params = json.dumps({ "jsonrpc": "2.0", "method": "host.get", "params": { "output":[ "hostid", "name" ], "selectInterfaces":[ "interfaceid", "ip", ] }, "id":2, "auth":self.get_auth() }) headers = {'content-type': 'application/json; charset=UTF-8'} r = myRequests.post(url=url, data=params, headers=headers) r.encoding = 'utf-8' data = r.json() self.Holist = data['result'] return self.Holist #獲取告警 def get_trig(self,hostid): url = '%s/api_jsonrpc.php' % self.Zabbix_Address params = json.dumps({ "jsonrpc":"2.0", "method":"trigger.get", "params": { "output": [ "triggerid", "description", "priority" ], "filter": { "value": 1, "hostid":hostid }, "sortfield": "priority", "sortorder": "DESC" }, "auth": self.get_auth(), "id":1 }) headers = {'content-type': 'application/json; charset=UTF-8'} r = myRequests.post(url=url, data=params, headers=headers) r.encoding = 'utf-8' data = r.json() if data['result']: # text = json.dumps(data,ensure_ascii=False) return data['result'] else: return None #告警信息入庫 def get_triggerlist(self): list = self.Holist if list: for i in range(0,len(list)): # ip = self._untostr(list[i]['interfaces']['ip']) trigger = self.get_trig(list[i]['hostid']) Level = {'1':'DISASTER','2':'HIGH','3':'AVERAGE','4':'WARNING','5':'INFORMATION',\ '6':'NOT CLASSIFIED'} if trigger != None: Trigger = self._untostr(trigger[0]['description']) level = self._untostr(trigger[0]['priority']) name = self._untostr(list[i]['name']) ip = self._untostr(list[i]['interfaces'][0]['ip']) Datatime = time.strftime("%Y-%m-%d %H:%M", time.localtime()) Time = int(time.time()) z_LastTime = Time - int(self.z_Intervals) w_LastTime = Time - int(self.w_Intervals) #這個地方先查詢在間隔時間段內有沒有存在相同數據,如果沒有就插入,有就跳過 zabbix_sql = "SELECT * FROM zabbix_trigger WHERE HOSTNAME='%s' and \ DESCRIPTION='%s' and TIME BETWEEN %d and %d " % (name,Trigger,z_LastTime,Time) z_data = db.select(zabbix_sql) if z_data: pass else: z_Inset_sql = "INSERT INTO zabbix_trigger(DATA,TIME,HOSTNAME,HOSTIP,DESCRIPTION,LEVEL)\ VALUES('%s',%d,'%s','%s','%s','%s');" % (Datatime,Time,name,ip,Trigger,Level[level]) db.insert(z_Inset_sql) #判斷間隔時間內wechat_sendmsg表中是否存在相同輸入,沒有則插入 wechat_sql = "SELECT * FROM wechat_sendmsg WHERE HOSTNAME='%s' and \ DESCRIPTION='%s' and TIME BETWEEN %d and %d limit 1" % (name, Trigger, w_LastTime, Time) w_data = db.select(wechat_sql) if w_data: pass else: w_Inset_sql = "INSERT INTO wechat_sendmsg(DATA,TIME,HOSTNAME,HOSTIP,DESCRIPTION,LEVEL)\ VALUES('%s',%d,'%s','%s','%s','%s');" % (Datatime, Time, name, ip, Trigger, Level[level]) db.insert(w_Inset_sql) #這裏思路是 設定時間內獲取告警信息存入zabbix表, #然後再另外設定一個時間寫入weixin表 這樣做是爲了一個時間範圍內不重複告警 else: print ("[!]獲取主機列表失敗,正在重新獲取...") self.get_auth() self.get_host() self.get_triggerlist() #ZABBIX 心跳 def run(self): while True: print ("[*]Zabbix心跳正常..") time.sleep(self.sleeptime) self.get_triggerlist()
第三部分:SQLite
# coding=utf-8 import sqlite3,os SQLiteDB = os.path.join(os.getcwd(), 'TriggerDB.db') DBCON = sqlite3.connect(SQLiteDB,check_same_thread=False) #多線程操作要開啓這個選項 DBCUR = DBCON.cursor() class SQLiteDB(object): @staticmethod def insert(sql): try: DBCUR.execute(sql) except sqlite3.Error as e: print ("[!]Insert Error! %s" % e.args[0]) DBCON.commit() @staticmethod def select(sql): data = [] try: DBCUR.execute(sql) data = DBCUR.fetchall() except sqlite3.Error as e: print ("[!]Slect Error!%s"% e.args[0]) return data #初始化,新建兩個表 @staticmethod def CreatTable(): zabbix_sql ="create table if not exists Zabbix_Trigger (DATA text,TIME integer,HOSTNAME text, \ HOSTIP text,DESCRIPTION text,LEVEL text);" weixin_sql = "create table if not exists Wechat_Sendmsg (DATA text,TIME integer,HOSTNAME text, \ HOSTIP text,DESCRIPTION text,LEVEL text);" try: DBCUR.execute(zabbix_sql) DBCUR.execute(weixin_sql) except sqlite3.Error as e: print ("[!]Creat Error! %s" % e.args[0])
第四部分:合體
# coding=utf-8 from Zabbix import Zabbix from ZabbixTriggerDb import SQLiteDB from WeChat import WeChat import os,sys,thread if __name__ == '__main__': db = SQLiteDB db.CreatTable() z = Zabbix() #zabbix類 w = WeChat() #wechat類 z.Zabbix_Address = 'http://Zabbix服務器地址' z.Zabbix_Username = 'zabbix用戶' z.Passwd = 'zabbix密碼' z.z_Intervals = 600 #zabbix告警入庫間隔 z.w_Intervals = 3600 #wechat告警入庫間隔 z.sleeptime = 10 #Zabbix心跳間隔 w.Intervals = 3 #告警檢測心跳間隔 w.xintiao = 2 #微信心跳間隔 z.get_auth() #zabbix token z.get_host() #zabbix hostlist z.get_triggerlist() #zabbix triggerlist if not w.Get_UUID(): print('[!]獲取uuid失敗,請重新運行!') print('[*]正在獲取二維碼圖片...') w.genQRCode() #獲取二維碼 while w.WaitForLogin() != '200': pass w.login() #登陸 w.webwxinit() #初始化 w.webwxgetcontact() #獲取好友列表 w.Wx_Views() #設置告警好友 #定義一個線程方法,加入zabbix運行線程與微信發送告警線程 def RUN(): thread.start_new(z.run, ()) thread.start_new(w.run, ()) RUN() #啓動微信心跳(讓微信保持在線狀態) w.Wx_heartBeatLoop()
最終效果: