• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

去吧接收警告管理器告警并发送钉钉


Recommended Posts

前言

功能:作为警告管理器的webhook接收器,提取需要的数据转发到钉钉群机器人的webhook

网框架:金酒

警告管理器版本:0.24

系统版本:ubuntu 20.04

功能比较简单,所以就随便写了,全部属于主要的包

原始json数据示例

{

接收者: 'web\\ ."挂钩",

状态' : '点火,

警报' : [{

状态' : '点火,

标签' : {

警报名称' : '节点,

实例' : '192.168.0.10 ',

作业:“rh7”:

严重性' : '严重'

},

注释' : {

描述: 'rh7节点断联已超过一分钟!

汇总' : '192.168.0.10下降'

},

始于' : ' 2022-04-28t 08:44:23.05 z ',

结束于' : ' 0001-01-01t 00:00:00 z ',

生成器URL ' : ' http://localhost。本地域:19090/图?' g0.expr=up==0\u0026g0.tab=1 ',

指纹: '726681bf4674e8a5 '

}],

groupLabels': {

警报名称' : '节点'

},

普通标签' : {

警报名称' : '节点,

实例' : '192.168.0.10 ',

作业:“rh7”:

严重性' : '严重'

},

commonAnnotations': {

描述: 'rh7节点断联已超过一分钟!

汇总' : '192.168.0.10下降'

},

外部网址' : 'http://192.168.0 ',

版本' : '4 ',

组密钥' : ' { } : { alert name=\ ' node \ ' } ',

截断警报' : 0

}

示例代码

model.go(定义结构体)

主包装

//定义接收数据数据的结构体

类型请求警报结构{

状态字符串json: '状态' '

开始卫星字符串JSON :“starts at”

EndsAt字符串json:'endsAt ' '

GeneratorURL字符串json:'generatorURL ' '

指纹字符串json: '指纹' '

标签reqalert label“JSON :”标签' '

注释请求警告注释` json: '注释' '

}

类型请求组标签结构{

Alertname字符串JSON :“alert name”

}

类型ReqCommonLabels结构{

Alertname字符串JSON :“alert name”

实例字符串json: '实例' '

作业字符串json: '作业' '

严重性字符串json: '严重性' '

}

类型ReqCommonAnnotations结构{

描述字符串" json: "描述

n"` Summary string `json:"summary"` } type ReqAlertLabel struct { Alertname string `json:"alertname"` Instance string `json:"instance"` Job string `json:"job"` Severity string `json:"severity"` } type ReqAlertAnnotations struct { Description string `json:"description"` Summary string `json:"summary"` } type RequestBody struct { // alertmanager传来的请求体 Receiver string `json:"receiver"` Status string `json:"status"` ExternalURL string `json:"externalURL"` Version string `json:"version"` GroupKey string `json:"groupkey"` TruncatedAlerts float64 `json:"truncatedAlerts"` Alert []ReqAlert `json:"alerts"` GroupLabels ReqGroupLabels `json:"groupLabels"` CommonLabels ReqCommonLabels `json:"commonLabels"` CommonAnnotations ReqCommonAnnotations `json:"commonAnnotations"` }
  • dingmsg.go(对接钉钉webhook,注意替换webhook的url)
package main
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)
type Md struct {
	Title string `json:"title"`
	Text  string `json:"text"`
}
type Ding struct {
	Msgtype  string `json:"msgtype"`
	Markdown Md     `json:"markdown"`
}
func DingMarshal(text string) string {
	// struct序列化为json
	var myjson Ding = Ding{
		Msgtype: "markdown",
		Markdown: Md{
			Title: "锄田测试钉钉机器人",
			Text:  text,
		},
	}
	va, err := json.Marshal(myjson)
	if err != nil {
		panic(err)
	}
	return string(va)
}
func PostUrl(jsondata string) {
	// 发起post请求
	// 替换钉钉群机器人的webhook
	url := "https://oapi.dingtalk.com/robot/send?access_token=123456"
	var jsonstr = []byte(jsondata)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonstr))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Content-Type", "application/json;charset=utf-8")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("钉钉webhook响应: %v\n", string(body))
}
func SpliceString(status, description, summary string) string {
    // 拼接字符串
	var rst strings.Builder
	rst.WriteString("# 测试钉钉机器人 \n\n")
	rst.WriteString("- status: " + status + " \n\n")
	rst.WriteString("- description: " + description + " \n\n")
	rst.WriteString("- summary: " + summary + " \n\n")
	return rst.String()
}
  • main.go
package main
import (
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
	"github.com/gin-gonic/gin"
)
func ParseJson(c *gin.Context) {
	// 解析AlertManager 传递的 json 数据
	var json RequestBody
	// 将数据解析到结构体中
	if err := c.ShouldBindJSON(&json); err != nil {
		// 返回错误信息
		c.JSON(http.StatusBadRequest, gin.H{
			"error": err.Error(),
		})
		return
	}
    // 遍历json中alerts数据
	for k, v := range json.Alert {
		// fmt.Printf("k: %v, v: %v\n", k, v)
		fmt.Printf("k: %d, status: %s, description: %s, summary: %s \n", k, v.Status, v.Annotations.Description, v.Annotations.Summary)
		text := SpliceString(v.Status, v.Annotations.Description, v.Annotations.Summary)
		va := DingMarshal(text)
		PostUrl(va)
	}
    // 返回给客户端的消息
	c.JSON(http.StatusOK, gin.H{
		"msg": "success",
	})
}
func main() {
	// 设置gin运行模式,可选:DebugMode、ReleaseMode、TestMode
	gin.SetMode(gin.ReleaseMode)
	// 关闭控制台日志颜色
	gin.DisableConsoleColor()
	r := gin.Default()
	// 只接受来自127.0.0.1的请求
	r.SetTrustedProxies([]string{"127.0.0.1"})
    // 路由组
	v1 := r.Group("/v1")
	{
		v1.POST("/alert", ParseJson)
	}
    // 设置程序优雅退出
	srv := &http.Server{
		Addr:    "127.0.0.1:19100",
		Handler: r,
	}
	go func() {
		if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
			log.Printf("listen: %s\n", err)
		}
	}()
	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("Shutting down server...")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server forced to shutdown:", err)
	}
	log.Println("Server exiting")
}

使用示例

  1. 导入依赖
go mod tidy
  1. 编译
go build -o goalert.bin ./main.go ./model.go ./dingmsg.go
  1. 运行
./goalert.bin
  1. 测试。使用python + requests 测试。python代码如下
import requests
# 注意json字符串的代码转为utf-8,否则会报Unicode的错误
json_firing = r"""
{"receiver":"web\\.hook","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"node","instance":"192.168.0.10","job":"rh7","severity":"critical"},"annotations":{"description":"rh7 192.168.0.10 节点断联已超过1分钟!","summary":"192.168.0.10 down "},"startsAt":"2022-04-28T08:44:23.05Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://localhost.localdomain:19090/graph?g0.expr=up+%3D%3D+0\u0026g0.tab=1","fingerprint":"726681bf4674e8a5"}],"groupLabels":{"alertname":"node"},"commonLabels":{"alertname":"node","instance":"192.168.0.10","job":"rh7","severity":"critical"},"commonAnnotations":{"description":"rh7 192.168.0.10 节点断联已超过1分钟!","summary":"192.168.0.10 down "},"externalURL":"http://192.168.0.10:19092","version":"4","groupKey":"{}:{alertname=\"node\"}","truncatedAlerts":0}
""".encode("utf-8").decode("latin1")
url = "http://127.0.0.1:19100/v1/alert"
resp = requests.post(url=url, data = json_firing, headers={"Content-Type": "application/json"})
print(resp.text)
  1. alertmanager配置略过

补充

  • 钉钉可以通过面对面建群的方式建一个单人群,单人群也能添加机器人。
  • 如果不知道alertmanager发送的json数据是什么样,可以写个服务端,直接接收数据不解析,在控制台原样输出字符串。示例代码如下:
package main
import (
	"fmt"
	"io/ioutil"
	"net/http"
)
func f1(w http.ResponseWriter, r *http.Request) {
	// 向客户端响应ok
	defer fmt.Fprintf(w, "ok\n")
	// 获取客户端的请求方式
	fmt.Println("method:", r.Method)
	// 获取客户端请求的body
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Printf("read body err, %v\n", err)
		return
	}
	fmt.Println("json: ", string(body))
}
func main() {
	http.HandleFunc("/", f1)
	http.ListenAndServe("127.0.0.1:8888", nil)
}
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now