Files
tamabox/internal/security/ipinfo/ipinfo.go
T
tamakyi e94c2b57ce 新增:安全告警邮件展示来源 IP 网络归属(CDN/云厂商/WAF)
- 新增 internal/security/ipinfo:基于 projectdiscovery/cdncheck v1.3.0
  (离线指纹、零外部请求)识别来源 IP 归属,识别失败折叠为展示标签,
  不中断告警链路
- autoban 告警回调注入归属识别;告警邮件新增「网络归属」行,
  命中 CDN/代理网络时提示「封禁可能无法拦截真实来源」
- 新增邮件模板渲染回归测试 internal/mail/mail_alert_test.go
- dev/s3stub:webp(cgo) 本地类型检查桩,替换 s3.go 做全模块编译验证,
  生产 Linux 构建不受影响;dev/overlay.json 指向该桩
- go mod tidy 顺带升级共享依赖(lipgloss/xid/testify 等),
  全模块类型检查与相关包测试通过
2026-09-08 13:25:20 +08:00

85 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package ipinfo 识别 IP 的网络归属(CDN / 云厂商 / WAF)。
// 基于 projectdiscovery/cdncheck 的离线签名(编译期嵌入,上游每周更新指纹),
// 判定过程零外部网络请求,适合 1C1G 小型服务器与告警链路使用。
// 当前用于安全告警邮件:展示扫描来源的网络归属,帮助管理员判断告警严重性。
package ipinfo
import (
"fmt"
"net"
"strings"
"sync"
"github.com/projectdiscovery/cdncheck"
)
var (
clientOnce sync.Once
client *cdncheck.Client
)
// getClient 懒加载判定客户端(签名数据编译期嵌入,New 无错误返回)。
func getClient() *cdncheck.Client {
clientOnce.Do(func() {
client = cdncheck.New()
})
return client
}
// Lookup 判定 IP 的网络归属。
// 返回:
// - label:展示用标签,如 "Cloudflare(CDN)"、"亚马逊(云厂商)";
// 未命中返回 "未识别(普通网络)",输入非法或判定出错返回 "识别失败"。
// - proxied:IP 是否属于 CDN/WAF/代理网络(用于提示"封禁可能无法拦截真实来源")。
//
// 任何内部错误都被折叠为展示标签,不向调用方传播——告警链路不应因归属识别失败而中断。
func Lookup(ipStr string) (label string, proxied bool) {
ip := net.ParseIP(strings.TrimSpace(ipStr))
if ip == nil {
return "识别失败(IP 格式异常)", false
}
matched, value, itemType, err := getClient().Check(ip)
if err != nil {
return "识别失败", false
}
if !matched || value == "" {
return "未识别(普通网络)", false
}
return fmt.Sprintf("%s(%s)", title(value), typeLabel(itemType)), true
}
// typeLabel 将 cdncheck 的 itemType 翻译为中文展示。
func typeLabel(itemType string) string {
switch itemType {
case "cdn":
return "CDN"
case "waf":
return "WAF"
case "cloud":
return "云厂商"
default:
return "未知类型"
}
}
// acronymProviders 需要保持全大写的提供商缩写(cdncheck 返回全小写,如 aws → AWS)。
var acronymProviders = map[string]bool{
"aws": true, "gcp": true, "oci": true, "gds": true, "ovh": true,
}
// title 将提供商名转为展示形式:缩写全大写(aws → AWS),其余首字母大写
// (cloudflare → Cloudflare)。
func title(s string) string {
if s == "" {
return s
}
lower := strings.ToLower(s)
if acronymProviders[lower] {
return strings.ToUpper(lower)
}
r := []rune(lower)
return strings.ToUpper(string(r[0])) + string(r[1:])
}