MiBeeSteward v0.3.0: 完整 L2 拓扑 + TLS 证书清单 + 容器镜像
接手一个不熟悉的局域网,第一件事是搞清楚里面有什么——办公室、机房、家里几个网段混接,没人留下文档,交换机上连着一堆没人叫得出名字的 MAC。MiBeeSteward 就是干这件事的:扫一遍网络,告诉你有哪些设备、它们是什么、它们之间怎么连的。
v0.3.0 是冲着前两个版本留下来的三个具体痛点去的:
- 拓扑画不全。v0.2.0 用 Bridge-MIB 拿到了「哪个 MAC 在哪个交换机端口后面」,但只此一招——跨厂商的邻居身份、VLAN-aware 的转发、STP 端口角色全瞎,拓扑图缺一大块。
- TLS 证书没人管。从 v0.1.0 起 TLS 服务就只抓了证书 CN 当指纹用,“这台设备上的 HTTPS 证书还有几天过期"根本答不出来。内网里一堆自签证书,过期那天服务就突然挂掉。
- 部署门槛高。之前只能下二进制 + 写 systemd unit,想快速试一下都不方便。
v0.3.0 一次解决:完整 L2 拓扑、TLS 证书清单、官方容器镜像。完整发布说明见 GitHub Release v0.3.0。想最快体验效果,拉镜像跑一行:
| |
--network host 是关键——容器在独立 netns 里会发现不到任何 MAC(下面会讲为什么)。打开 Web UI,等几分钟扫完,设备列表 / 拓扑图 / TLS 证书面板全部自动填上。
拓扑:现在能画完整了
你能看到什么:一台交换机为中心的整张 L2 拓扑图。每台设备按类型上色(路由器/交换机/摄像头/打印机/IoT…),连线按发现协议上色(CDP / LLDP / Bridge-MIB / Q-BRIDGE / STP),没识别出来的邻居走虚线。点任何节点跳到设备详情页,看到这台设备的所有邻居和它们的 name/IP/type。
flowchart TD
SW["交换机<br/>SNMP 可达"] --> PROBE["三类 MIB 并行探测"]
PROBE --> CDP["CDP-MIB<br/>厂商邻居"]
PROBE --> QB["Q-BRIDGE-MIB<br/>VLAN 转发"]
PROBE --> STP["STP-MIB<br/>端口角色"]
CDP --> EDGE["device_neighbors<br/>按 protocol 打 tag"]
QB --> EDGE
STP --> EDGE
classDef dev fill:#E3F2FD,stroke:#1565C0,color:#1565C0
classDef proc fill:#FFF3E0,stroke:#E65100,color:#BF360C
classDef store fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
class SW dev
class PROBE,CDP,QB,STP proc
class EDGE store为什么需要三个 MIB:v0.2.0 只靠 Bridge-MIB,跨厂商环境会漏边。Cisco 系交换机的邻居身份靠 CDP-MIB 拿(device id、平台串、IP);VLAN-aware 的转发项要查 Q-BRIDGE-MIB(dot1qTpFdbPort);STP 端口角色(哪条边在转发 / 哪条被阻塞 / 谁是 root bridge)走 STP-MIB(dot1dStp)。三个 MIB 各管一段,邻居边按协议打 tag 后统一存进 device_neighbors。
IF-MIB ifName 解析把数字 ifIndex 翻译成人能读的名字——拓扑图上看到的是 GigabitEthernet0/1,不是 1011 这种鬼东西。
邻居身份推断也补全了:CDP/LLDP 邻居的平台串(比如 Cisco ISR4321)喂给 v0.2.0 的 RuleClassifier 推断出厂商/型号/类型,再用 MAC 做合并键,把拓扑里的"邻居"和设备表里的"已知设备"对上号。v0.2.0 里 neighbor_device_id 一直是 NULL——这台设备明明在线、明明被探测到了,但在拓扑里就是"无名氏”。v0.3.0 在查询时实时解析,详情页的 Neighbors 面板才能展示出有名字的邻居表。
TLS 证书清单:过期前提前知道
你能看到什么:每台设备上每个 TLS 服务的完整证书链。不是只看 leaf——issuer 链一路追到底。前端每个端口一行可点击的入口,状态色边框一眼分清:绿色=有效 / 琥珀色=15 天内过期 / 红色=已过期。天数 badge 直接告诉你还剩几天。点开 CertificateModal 看完整证书链 + PEM,带复制按钮。
这解决了什么问题:内网里常见的坑是 HTTPS / LDAPS / IMAPS 服务用了自签证书,运维忘了续期,证书过期那天服务突然挂掉,排查半天才发现是证书问题。v0.3.0 让你一眼看到「未来 15 天哪些证书要过期」,提前处理而不是事发后救火。
flowchart TD
SCAN["扫描 TLS 端口"] --> CC["CollectCertChain<br/>8 协议共享核心"]
CC --> CHAIN["拿完整证书链<br/>leaf + issuers"]
CHAIN --> DB[("host_tls_certs<br/>按 not_after 索引")]
DB --> UI["前端面板<br/>状态色 + 天数 badge"]
classDef proc fill:#FFF3E0,stroke:#E65100,color:#BF360C
classDef store fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
classDef out fill:#E3F2FD,stroke:#1565C0,color:#1565C0
class SCAN,CC,CHAIN proc
class DB store
class UI out怎么实现的:8 个 TLS-wrapped handler 共享一个 CollectCertChain 核心——https / ldaps / smtps / imaps / pop3s / ftps / ircs / telnets,handler 总数从 21 涨到 29。每张证书抽取 Subject/Issuer/SAN、serial、有效期、签名与密钥算法(RSA/ECDSA/Ed25519 + 位数)、CA/自签标志、SHA-256 指纹、PEM。
| Handler | 默认端口 | Handler | 默认端口 |
|---|---|---|---|
https | 443 | imaps | 993 |
ldaps | 636 | pop3s | 995 |
smtps | 465 | ftps | 990 |
ircs | 6697 | telnets | 992 |
数据落到 host_tls_certs 表,每个端口的证书链里一张证书一行(cert_index 0 = leaf,1..N = issuer)。在 (ip, port) 和 not_after 上建索引——前者服务「这台设备这些端口的证书」这种查询,后者服务「未来 N 天过期的证书」这种扫描。读取走 GET /api/v1/devices/{id}/certificates。保留期 retention.host_tls_certs_days 默认 30 天。
容器镜像:一行命令跑起来
你能做什么:v0.3.0 第一次发布官方多架构容器镜像(linux/amd64 + linux/arm64)到 GHCR。不用下二进制、不用写 systemd unit、不用装 Go 工具链——docker pull 就能跑。arm64 意味着树莓派、Banana Pi 这些 ARM 盒子也能直接用同一个镜像。
镜像 tag 跟着 release::0.3.0 / :0.3 / :latest / :sha-xxx。每次打 v* tag 自动发布。
三种部署形态,按用途选:
| Profile | 探测保真度 | 适用场景 |
|---|---|---|
bridge(默认) | TCP/SNMP/HTTP 正常;MAC/ARP 退化 | UI demo、开发 |
host(推荐) | ≈ 裸金属 | 生产扫描 |
macvlan | 容器自有 LAN IP,ARP 正常 | 容器作为 LAN 设备 |
bridge 看起来是 docker 默认值所以最方便,但对 scanner 来说它是个陷阱——实测在 31 台设备的测试 LAN 上,默认 bridge 发现的设备 MAC 数是 0,host 网络是 30。根因在 /proc/net/arp:scanner 的被动发现要读内核 ARP 缓存,而 NAT 容器里的 ARP 表只看得到 bridge gateway,LAN 上其他主机全瞎。所以文章开头那个 docker run 命令才特意写了 --network host。详细的根因分析和三种 profile 的取舍在技术深度博文里。
这个镜像是非特权变体——LLDP/CDP 的 raw frame 发送 + eBPF 被动观测器 ship 成 no-op stub(raw socket 需要 CAP_NET_RAW)。需要这些能力的人用 make docker-build-priv 在本地构建特权变体。普通 docker run 在 host 网络下已经能扫出绝大多数设备—— Bridge-MIB / Q-BRIDGE / CDP / STP / TLS / 端口扫描全部不依赖 raw socket。
运维改进
v0.3.0 还修了一批 v0.2.0 留下的运维债:
- Retention 加固:
device_neighbors和host_services在 v0.2.0 里没有 sweeper,会无限增长。v0.3.0 补上——默认device_neighbors90 天、host_services30 天,每表可配,带days<=0安全 guard(不会一次清空全表)。 - 测试覆盖:
taskservice(扫描任务状态机,之前零测试)和指纹 golden test(用真实证据样本挡识别质量回归)。 - 指纹库扩展:
snmp-data.yaml加了 consumer/SMB 的 sysObjectID 前缀(ASUS、D-Link、Zyxel、Tenda、DrayTek);新增lldp-cdp.yaml规则集,CDP/LLDP 平台串的识别规则更全。 - 修复清单:移除已废弃的
tls.VersionSSL30(staticcheck SA1019);gofmt + golangci-lint v2 清理(QF1008、未用参数、内嵌选择器);一个潜伏的 sqlc v1.27.0 codegen bug(某条 query 注释里的非 ASCII 字符污染了相邻 query 的代码生成)。 - CI:新增
docker-build冒烟测试 job,在每个 PR 上启动镜像并等/health,Dockerfile/compose 回归在打 tag 之前就挡住。
升级
二进制路径:直接替换。容器路径:docker pull 新 tag 重启。
新表 host_tls_certs 走 CREATE TABLE IF NOT EXISTS,自动建,无需手工迁移。新增的 retention.host_tls_certs_days / retention.device_neighbors_days / retention.host_services_days 都有默认值,不配也工作。现有设备数据原样保留。
相关链接
- MiBeeSteward GitHub
- MiBeeSteward v0.3.0 Release Notes
- v0.2.0 功能概览
- 技术深度博文(Docker 网络模式与探测保真度、TLS 证书链采集架构、L2 拓扑为何要三个 MIB)
如果你接手过一个没人留下文档的 LAN,想知道里面到底跑了什么、哪些设备连在哪个交换机口、HTTPS 证书什么时候过期——v0.3.0 就是干这个的。一行 docker run --network host 跑起来,等几分钟扫完,自己看结果。