Files
tamabox/.workbuddy/memory/2026-10-30.md
T
2026-11-02 17:17:06 +08:00

7.4 KiB
Raw Blame History

2026-10-30

蜜罐周期安全汇总(周报/月报 + 地图 + 邮件)

用户需求:以周/月为单位汇总安全状况,把嗅探 IP 通过公共服务查地理位置,汇总到地图, 发到邮箱。起因是他发现线上有人隔一段时间换不同 IP 探测蜜罐。

关键认知:ipinfo 与 geoip 是两个维度(用户亲自纠正)

用户反馈「ipinfo 只能查机房,肉鸡全显示『未识别(普通网络)』」。原因是 internal/security/ipinfo 走 projectdiscovery/cdncheck 的离线 ASN 指纹,只认云厂商/CDN/WAF。 它答的是「是不是代理」,不是「在哪儿」。肉鸡(家宽、企业宽带、移动网络)必然是后者, 所以安全汇总必须另建一套真正的 IP 地理库 → 新包 internal/security/geoip。

新增文件

  • internal/security/geoip/geoip.go — ip-api 批量打底(100/批、1.4s 节流)+ 腾讯精修国内 (只精修前 200 个,一 IP 一请求)。Result{Geo, Unresolved, Requests, Failures} 三态。
  • internal/security/report/{period,aggregate,mapdraw,html,geo,countryname}.go
  • internal/db/{ipgeo,security_report}.go — ip_geo_cache(主键 = LimitKey)、 security_report_logs(period_key 唯一索引保证幂等)
  • internal/cmd/report.go — honeypot report --period week|month + 30 分钟一轮的调度器
  • templates/mail/security-report.html

踩过的坑(都要记住)

  1. ip-api 免费版很慢:22 个 IP 实测 4–10 秒,且同一批重复请求更慢。最初设 15s 超时 → 第一次真实数据就整批超时、定位率 0%。现在 defaultTimeout = 45s + 重试一次。
  2. 缓存毒化:早先把所有 miss 都写缓存并 Failed=true(TTL 3 天)→ 一次网络抖动 等于把整批 IP 钉成「查不到」三天,报告定位率莫名变 0 且无任何线索。 现在只有服务明确回答没数据才允许写失败缓存;请求失败绝不写。
  3. addrs 不能从 map 取顺序:腾讯精修只取前 N 个,若按 map 迭代(随机)取, 每次运行精修到的 IP 都不同,配额花在长尾上。必须按 keys 原始顺序(= 命中数降序)建。
  4. 地图标签会叠出假数字:同一都市圈两个城市坐标只差 0.01°,标签精确重叠, 「AU 55」+「AU 30」叠成了看起来像 AU 305 的字符串 —— 读图的人会当结论。 已加 labelPacker(不重叠才画,右侧放不下换左侧)。
  5. 不能无脑把腾讯结果的国家码写成 CN:provider=tencent 时海外 IP 也走这条路。 现在从 ad_info.nation 精确判「中国」才给 CN。

合规(地图)

自绘底图零国界,只画经纬网 + 气泡 + 度数刻度;真实底图交给腾讯地图(配 key 才有, 未配给申请指引)。代码里不预置任何 key,测试里有禁词断言(googleapis/mapbox/osm/bing)。 报告里 HK/MO/TW 统一显示为「中国香港 / 中国澳门 / 中国台湾」(countryname.go), 且常见国家改用中文名(报告是给中文读者看的)。

端到端验证(WSL Debian + PG 17.11)

854 条仿真数据 / 22 个来源:首跑 36s(真实 ip-api)、次跑 1.3s(缓存命中), 定位覆盖 22/22、14 个国家/地区、未能定位 0 次。

顺手修的(别人改动留下的)

  • internal/route/honeypot_test.go 的 captureStore 缺 3 个新方法 → 整个 route 包编译不过
  • templates/render_test.go 的 mockLoggedUser 缺 TelegramChatID → 3 个渲染测试失败

蜜罐邮件提醒功能审阅(用户要求,重点找 bug 与改进点)

审阅报告:outputs/review-honeypot-mail-2026-10-30.md。基线测试全绿(mail + route 蜜罐相关)。

结论分级

  • P1(应修)autoban.alerted 去重口径不一致:map[string]bool 永不过期,且 cleanup() 从不清理它 → 同一 IP 的告警一生只发一次;而蜜罐侧 dedup 用 24h TTL。两条链路口径不同, 长期盯防同一来源会漏掉后续攻击活动。建议改成带时间戳的 map + cleanup 按窗口清理。
  • P2(建议修)蜜罐告警空收件人不短路:route/honeypot.go:alert() 没有 SecurityAlertEmail == "" 前置判断(autoban 侧有 alertEmail != "")。蜜罐开着但没配收件人时, 每次凭据提交都走一遍无效 SMTP 拨号 + 留 Warn 日志,与「未配置静默跳过」预期不符。
  • P2(边界)邮件明文密码:SendHoneypotAlertMail 把明文 password 渲染进 HTML。代码已意识到 是敏感物。建议维持明文(功能价值=运维立刻拿到字典)+ 强化「收件人必须是个人邮箱、禁群组」的 配置警示,而非一刀切掩码。

审阅中确认「已做对、无需改」的点(值得记住的设计)

  • 凭据类型层面不入库:db.HoneypotHit 结构体没有 Username/Password 字段,编译期保证; 明文只落 0600 按天 JSONL。
  • 蜜罐独立于 enable_auto_ban(RecordHoneypot 只看 HoneypotEnabled)。
  • 纯 GET 探测不立即封(留「GET 表单→POST 提交」的咬钩机会)。
  • 精确 IP 与 /64 封禁键分离;body 只读一次先提取凭据再判封禁;clipRunes 按 rune 截断防非法 UTF-8。

三项已修(commit 036156b)

  1. P1 autoban.alerted 去重口径:map[string]bool → map[string]time.Time,新增 alertCooldown = 24h 常量 + alertedRecently() 辅助 + cleanup() 清理过期项。 与蜜罐侧 alertDedupTTL=24h 对齐。新增 TestAlertCooldownExpires(回拨 alerted 时间戳 模拟冷却期已过,断言能再次告警)。
  2. P2 空收件人短路:route.go 注入 sendAlert 前判 SecurityAlertEmail 空值,空则置 nil, alert() 的 h.sendAlert == nil 自然短路。(autoban 侧本就有 alertEmail != "" 判断,无需改。)
  3. P2 明文密码警示:conf/static.go 注释 + app.sample.ini 注释强化「蜜罐邮件含明文密码, 收件人必须个人邮箱、禁群组/分发列表」。维持明文不改掩码(功能价值=运维立刻拿到字典)。

验证:autoban/route/mail/conf 测试全绿,go build ./... 通过,gofmt 干净,行尾未破坏(LF)。

更新日志文档 + 首页间距修复

  1. 更新日志:outputs/changelog-2026-10-30.md,站长口吻、口语化,只写用户可感知的功能 (数据面板/Telegram 通知/找回提问/图片压缩/404 页/验证码不丢正文/私聊完整记录)。 蜜罐、备份、迁移等纯后台内容刻意不写。注意:Telegram 条目依赖生产配好 [notify] 的 bot token。
  2. 生产配置答疑:蜜罐子项全有 sanitizeHoneypot 兜底,无需补;真正要显式加的是 honeypot_cred_dir、报告配置段(report_email 留空回退 security_alert_email)、[notify] 可选段。 生产 ban_window_seconds=10 比默认 60 短,已提示用户确认是否有意。
  3. 首页间距修复(ea9ba3e):templates/home.html 里 tb-grid 之后的三张独立卡片 (域名迁移/一点提醒/近期更新)彼此零间距挤成一团。修法:.tb-grid ~ .uk-card { margin-top: 16px } (模板内联 <style>,不碰 base.css,无需 bump StaticVersion)。 截图验证:headless=old + file:// 预览页,亮暗两主题间距均正常。 坑:headless Chrome 的 --screenshot 相对路径写不进(Failed to write file),必须用绝对路径。