0x01 前言
为了能及时收到zabbix的告警提醒,我一直以来都使用企业微信作为除邮件以外的接收平台。因为时效性的问题,电子邮件并不是很好的试试告警提醒途径,反而即时通讯软件则满足这个需求。
我们常用的微信并不适合这么频繁的信息推送,而私密性较高的企业微信的可定制化正是我需要的。在过去一年多的时间里,免费、无广告又及时的企业微信是我告警推送的首选:
0x02 准备
使用该脚本推送告警信息需要准备以下内容:
- 企业微信的企业ID
- 企业微信的部门ID
- 企业微信的应用ID
- 企业微信的应用Secret
相对于阿里钉钉,企业微信的信息获取则简单得多。首先得又一个企业微信账号,而企业微信的注册非常简单,登入以下页面并填写相关内容即可:
然后定位到以下页面即可获取企业ID:
而部门ID则需要先建立部门,而后即可在以下按钮中找到:
最后来到“应用与小程序”页面,建立一个应用:
完成后进入应用的配置界面,即可找到相应的信息:
将上面的信息记录下来,以备后用。
0x03 配置文件生成器
因为配置脚本所需的配置文件较为复杂,我特意编写一个生成器,通过生成器的交互可减少不必要的脑部活动。
通过python3执行配置文件生成器:
python3 configuration_file_create_assistant.py
在我写这篇文章的时候发现该脚本又逻辑上的错误,我将在近期进行修正。
完成后即可在该文件的目录下找到配置文件:alarm_sender.conf,里面的内容如下:
{ 'wechat': [ { 'wc_corpid': '***********************', 'wc_corpsecret': '***********************', 'wc_group_id': '2', 'wc_app_id': '1' } ] }
如果有多个应用或需要将告警信息推送到多个部门甚至多个企业,可以跟随配置文件生成器的提示继续添加。
0x04 逻辑
因为只是一个简单的信息推送,所以逻辑也较为简单,流程如下:
- 检查临时文件中是否存在token,若是则校验有效期,若否或已过期则获取新token
- 利用token调用信息推送API推送信息
- 写入日志与临时文件
检查token的函数如下:
def chk_token(self): # 尝试读取文件 try: r = open(tmp_file_path, 'r') except FileNotFoundError: return None else: r_content = r.readline() r_dict = json.loads(r_content) r.close() # 尝试获取旧token try: wc_token_last = r_dict["wechat"][self.corp_id]["token"] except KeyError: return None else: wc_token_timestamp_last = r_dict["wechat"][self.corp_id]["timestamp"] # 判断有效期 if int(timestamp_now) - int(wc_token_timestamp_last) > 6600000: return None else: return wc_token_last
首先尝试读取文件,如果文件不存在,则返还None;如果文件存在,将格式化为json,而后尝试读取相应的key;若key存在则判断有效期,若不存在则返还None;最后有效期有效则返还该key所对应的value,否则返还None。
如果检查token的函数返还None,则需要重新获取,函数如下:
def get_token(self): get_access_token_url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' \ + self.corp_id + '&corpsecret=' + self.corp_secret r = requests.get(get_access_token_url, timeout=5) r_content = r.content.decode() r_content_dict = eval(r_content) errmsg = r_content_dict['errmsg'] if errmsg == "ok": new_token = r_content_dict['access_token'] write_tmp(self.corp_id, new_token) return True, new_token else: return False, r_content_dict['errmsg']
该部分的内容比较简单,只是一个HTTP GET的请求,最后做了一个简单的响应判断。因为时间的关系,这部分目前还没有编写后续动作的部分。
获取到新token后需要写入到临时文件,减少API调用的次数。因为token有一个较长的有效期,所以可以重复使用。而相关的函数如下:
def write_tmp(corp_id, new_token): r_tmp = open(tmp_file_path, 'r') tmp_content = r_tmp.read() r_tmp.close() try: tmp_dict = json.loads(tmp_content) except json.decoder.JSONDecodeError: tmp_dict = dict() if "wechat" in tmp_dict.keys(): try: del tmp_dict["wechat"][corp_id] except KeyError: pass else: tmp_dict["wechat"][corp_id] = {"timestamp": timestamp_now, "token": new_token} else: tmp_dict["wechat"] = {corp_id: {"timestamp": timestamp_now, "token": new_token}} r_tmp = open(tmp_file_path, 'w') r_tmp.write(json.dumps(tmp_dict)) r_tmp.close()
该部分主要是json的生成,层级关系比较复杂,最后将生成好的json写入文件。
一切顺利的话,即可用token向企业微信内创建好的应用推送信息,函数如下:
def send_msg(self, access_token, msg_content): post_content = { "text": { "content": msg_content }, "toparty": self.division_id, "msgtype": "text", "agentid": self.app_id } msg_content = json.dumps(post_content, ensure_ascii=False) url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + str(access_token) r = requests.post(url, msg_content.encode('utf-8'), timeout=5) r_content = r.content.decode() output_content = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) + ' wechat ' + r_content return output_content
同样是HTTP请求,但这里使用POST方式,而需要的信息全包含在HTTP payload内,最后返还日志。
0x05 调用
以下是一个简单的调用函数:
def push_to_wechat(data_content): corp_id = data_content['corp_id'] corp_secret = data_content['corp_secret'] division_id = data_content['division_id'] app_id = data_content['app_id'] msg = data_content['msg'] wechat_main = EnterpriseWeChat(corp_id, corp_secret, division_id, app_id) last_wc_token = wechat_main.chk_token() # 若旧token无效则获取新token if last_wc_token is None: new_wc_token = wechat_main.get_token() if new_wc_token[0]: wc_token = new_wc_token[1] else: return None # 若旧token有效则使用旧token else: wc_token = last_wc_token send_msg = wechat_main.send_msg(wc_token, msg) write_log(send_msg) return send_msg
首先将需要的参数赋值给对应的变量,而后跟随上述的逻辑执行,最后无论成功与否都会写入日志。
0x06 结语
这脚本有别于我之前用的版本,虽然大体上一致,但精简了很多冗余代码和删减了很多没必要的函数。这可能会产生BUG,如果有,欢迎在GitHub上提issues,如果有功能上的需求,可以给我发邮件。
- GitHub:alarm_sender
- GitLab: alarm_sender