修改监控为证书监控支持文件导入和smtp监控

监控支持多渠道通知
将静态文件打包到二进制文件
This commit is contained in:
v-me-50
2025-07-08 16:45:28 +08:00
parent b347ade900
commit e4917d7caf
396 changed files with 2263 additions and 584 deletions

View File

@@ -0,0 +1,212 @@
package monitor
import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"net"
"net/http"
"net/smtp"
"strings"
"time"
)
// buildCertChainRecursive 构建证书链树结构
func buildCertChainRecursive(certs []*x509.Certificate, index int) *CertNode {
if index >= len(certs) {
return nil
}
node := &CertNode{
CommonName: certs[index].Subject.CommonName,
Subject: certs[index].Subject.String(),
Issuer: certs[index].Issuer.String(),
}
if index+1 < len(certs) {
child := buildCertChainRecursive(certs, index+1)
if child != nil {
node.Children = append(node.Children, child)
}
}
return node
}
// checkChainRecursive 递归检查链条完整性
func checkChainRecursive(node *CertNode, level int) error {
if node == nil || len(node.Children) == 0 {
return nil
}
for _, child := range node.Children {
if node.Issuer != child.Subject {
return fmt.Errorf("证书链第 %d 层 [CN=%s] 的 Issuer 与第 %d 层 [CN=%s] 的 Subject 不匹配,链条断裂",
level, node.CommonName,
level+1, child.CommonName,
)
}
if err := checkChainRecursive(child, level+1); err != nil {
return err
}
}
return nil
}
// Check 检查证书链的完整性和有效性
func Check(certs []*x509.Certificate, host string, advanceDay int) (result *CertInfo, err error) {
result = &CertInfo{}
if len(certs) == 0 {
return nil, fmt.Errorf("未获取到服务端证书")
}
leafCert := certs[0]
// 提取 CA 名称Issuer 的组织名)
result.CA = "UNKNOWN"
if len(leafCert.Issuer.Organization) > 0 {
result.CA = leafCert.Issuer.Organization[0]
} else if leafCert.Issuer.CommonName != "" {
result.CA = leafCert.Issuer.CommonName
}
result.CommonName = leafCert.Subject.CommonName
result.NotBefore = leafCert.NotBefore.Format("2006-01-02 15:04:05")
result.NotAfter = leafCert.NotAfter.Format("2006-01-02 15:04:05")
result.DaysLeft = int(leafCert.NotAfter.Sub(time.Now()).Hours() / 24)
result.SANs = strings.Join(leafCert.DNSNames, ",")
result.SignatureAlgo = leafCert.SignatureAlgorithm.String()
sha256Sum := sha256.Sum256(leafCert.Raw)
result.Sha256 = hex.EncodeToString(sha256Sum[:])
// 构建证书链树结构
result.CertChain = buildCertChainRecursive(certs, 0)
// 用系统根证书池校验是否受信任
roots, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("加载系统根证书失败:%v", err)
}
if roots == nil {
roots = x509.NewCertPool()
}
intermediates := x509.NewCertPool()
for _, intermediate := range certs[1:] {
intermediates.AddCert(intermediate)
}
opts := x509.VerifyOptions{
DNSName: host,
Roots: roots,
Intermediates: intermediates,
}
if _, err := leafCert.Verify(opts); err != nil {
result.Valid = false
result.VerifyError = fmt.Sprintf("证书验证失败:%v", err)
return result, nil
} else if result.DaysLeft <= advanceDay {
result.VerifyError = fmt.Sprintf("证书即将过期,剩余 %d 天,请及时更新", result.DaysLeft)
} else if err := checkChainRecursive(result.CertChain, 1); err != nil {
result.VerifyError = fmt.Sprintf("证书验证成功,但在检查真实获取到的证书链时出现问题:\n%v\n可忽略仅作提醒如遇到ssl证书问题可作为排查方向", err)
}
result.Valid = true
return result, nil
}
// CheckHttps 严格检查 HTTPS 证书链,基于 CertChain 结构检查链完整性
func CheckHttps(target string, advanceDay int) (result *CertInfo, err error) {
// 解析 host 和 port
host, port, err := net.SplitHostPort(target)
if err != nil {
host = target
port = "443"
}
// 校验 host
if net.ParseIP(host) == nil {
if _, err := net.LookupHost(host); err != nil {
return nil, fmt.Errorf("无效的域名或 IP%v", err)
}
}
// 拼接 HTTPS URL
url := fmt.Sprintf("https://%s", net.JoinHostPort(host, port))
// 构建 HTTP 客户端
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Timeout: 5 * time.Second,
}
// 发送请求
resp, err := client.Get(url)
if err != nil {
return nil, fmt.Errorf("无法建立 HTTPS 连接:%v", err)
}
defer resp.Body.Close()
// 获取证书链
certs := resp.TLS.PeerCertificates
return Check(certs, host, advanceDay)
}
// CheckSmtp 检查smtp
func CheckSmtp(target string, advanceDay int) (result *CertInfo, err error) {
// 解析 host 和 port
host, port, err := net.SplitHostPort(target)
if err != nil {
host = target
port = "465" // 默认端口
}
// 校验 host
if net.ParseIP(host) == nil {
if _, err := net.LookupHost(host); err != nil {
return nil, fmt.Errorf("无效的域名或 IP%v", err)
}
}
// 如果端口是 465使用 TCP
if port == "465" {
return CheckTCP(host, port, advanceDay)
}
// 建立smtp连接
conn, err := smtp.Dial(host + ":" + port)
if err != nil {
return nil, fmt.Errorf("无法建立 SMTP 连接:%v", err)
}
defer conn.Close()
// 升级到 TLS
if err := conn.StartTLS(&tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}); err != nil {
return nil, fmt.Errorf("无法升级到 TLS%v", err)
}
// 获取证书
state, ok := conn.TLSConnectionState()
if !ok {
return nil, fmt.Errorf("无法获取 TLS 连接状态")
}
certs := state.PeerCertificates
return Check(certs, host, advanceDay)
}
// CheckTCP 通过 tcp 连接检查证书
func CheckTCP(host, port string, advanceDay int) (result *CertInfo, err error) {
// 建立 tcp 连接
url := fmt.Sprintf("%s:%s", host, port)
conn, err := tls.Dial("tcp", url, &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
})
if err != nil {
return nil, fmt.Errorf("无法建立tcp连接%v", err)
}
defer conn.Close()
// 获取证书链
certs := conn.ConnectionState().PeerCertificates
return Check(certs, host, advanceDay)
}