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,如果有功能上的需求,可以给我发邮件。