L1ghtn1ng/traceguard

GitHub: L1ghtn1ng/traceguard

一款基于 Linux eBPF 的 Go 语言安全工具,实现进程级 DNS 监控与可观测性并支持内核态主动阻断。

Stars: 1 | Forks: 1

# TraceGuard [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/L1ghtn1ng/traceguard) TraceGuard 是一个 Go 1.26 Linux 安全工具,使用内核 eBPF 子系统来: - 观察 UDP 和 TCP 53 端口上的出站 DNS 查询 - 报告发出 DNS 请求的进程 - 使用 `/proc` 中的进程路径、argv、UID、PPID 和父进程元数据丰富事件 - 检测出站 DNS-over-TLS 解析器连接 - 检测已配置的 DNS-over-HTTPS 解析器连接 - 跟踪 `execve` 和 `execveat` 活动,使新启动的程序可见 - 可选地阻止来自本地或远程黑名单域的 DNS 查询 - 应用精确匹配允许规则,这些规则优先于精确匹配阻止规则 - 支持后缀策略,如 `*.example.com` 和 `suffix:example.com` - 通过 HTTP 公开运行状况和 Prometheus 风格的指标 - 可选地本地归档 JSON 事件并导出到 HTTPS 收集器 - 支持带有持久重试排队的批处理认证 HTTPS 导出 - 支持 HTTPS 事件导出的可选 gzip 和 mTLS - 可选地使用 Kubernetes 命名空间、Pod、节点、工作负载、服务账户、容器和镜像元数据丰富 Pod 范围事件 - 在部署前运行内置的环境医生检查 在阻止模式下,TraceGuard 默认缓存远程黑名单 6 小时,并以相同频率刷新。 ## 设计 TraceGuard 使用两个 eBPF 程序: - `cgroup_skb/egress` 解析端口 53 上的出站 UDP 和 TCP DNS 数据包,发出 DNS 遥测,并在启用阻止时丢弃匹配的查询 - `cgroup/connect4` 和 `cgroup/connect6` 观察解析器端点连接以进行 DoT 和已配置的 DoH 端点,并在启用阻止时阻止匹配的端点 - `tracepoint/syscalls/sys_enter_execve*` 发出进程执行事件 用户空间服务: - 在加载到 BPF 哈希映射之前规范化阻止列表输入 - 支持本地和远程规则源中的 `block:` 和 `allow:` 策略条目 - 支持策略引擎中的精确和域后缀规则 - 将远程黑名单缓存到磁盘并进行原子文件替换 - 使用有界元数据缓存从 `/proc` 丰富事件记录 - 可将结构化事件存档到本地 JSONL 文件并进行轮换 - 可将结构化事件批量导出到 HTTPS 端点 - 可将失败的导出批次持久化到磁盘以供后续重放 - 可以从 Kubernetes API 使用现有的 `pod_uid` 信号丰富 Pod 范围事件 - 默认以换行符分隔的 JSON 格式输出记录,也可以输出文本日志 - 当配置了指标地址时导出 `/health` 和 `/metrics` - 在 BPF 程序中使用有界解析和固定大小缓冲区 - 避免调用外部命令或执行获取的内容 ## 要求 - 挂载在 `/sys/fs/cgroup` 的 cgroup v2 - eBPF 对 cgroup 出口和跟踪点的支持 - 等效于 `CAP_BPF`、`CAP_NET_ADMIN`、`CAP_PERFMON`、`CAP_SYS_ADMIN` 和 `CAP_SYS_RESOURCE` 的权限 - 用于 `execve` 探测的跟踪点性能事件访问权限,当 `CAP_PERFMON` 不可用时可能需要降低 `kernel.perf_event_paranoid` - Go 1.26 - `go generate` 所需的 `clang` 注意: - 阻止是在标准化 DNS QNAME 上进行的精确匹配,针对经典 UDP/TCP 53 端口 - 允许规则是精确匹配,并优先于精确匹配阻止规则 - 后缀规则匹配域及其任何子域,例如 `*.example.com` 或 `suffix:example.com` - `*` 为 DNS 名称和可识别解析器流量启用拒绝所有策略,并使用显式允许规则打孔 - DNS QNAME 匹配不区分大小写(ASCII) - DoT 支持是基于端点的:TraceGuard 可以检测发往 853 端口的外部连接并阻止已配置的 DoT 解析器端点 - DoH 支持也是基于端点的:TraceGuard 可以检测并阻止已配置的 HTTPS 解析器端点,但它无法恢复加密的内部 DNS 查询名称 - 通配符解析器模式不会将每个 HTTPS 连接都视为 DoH;仅当端口 443 匹配显式 DoH 端点规则或 CIDR 时才将其分类为 DoH - DoT 和 DoH 端点规则配置为 `dot://resolver.example` 或 `https://resolver.example/dns-query`;也支持精确的 IPv4 和带括号 IPv6 端点文字 - 裸 IP 文字如 `1.1.1.1` 或 `[2606:4700:4700::1111]` 被视为 DoH(443)和 DoT(853)的解析器例外 - 裸 CIDR 文字如 `1.1.1.0/24` 或 `2606:4700:4700::/48` 对 DoH 443 和 DoT 853 上的解析器范围采用相同处理 - 默认将日志写入 `/var/log/traceguard/traceguard.log`,在 1 GiB 处轮换并保留最近 5 个轮换文件 - 默认从 `/proc` 缓存进程元数据 10 分钟以减少查找开销 - Kubernetes 丰富是可选的、API 驱动的,并以观察到的 Pod UID 为键 - 在解析 DNS 之前解析常见的 IPv6 扩展报头 - 在阻止模式下,对于无法安全检查的分段 TCP DNS 查询和分片 IPv6 DNS 数据包,拒绝而不是允许 - 精确域策略可在内核阻止模式下强制执行;后缀和通配符域策略可用于观察和干运行工作流,但在强制阻止模式下此内核路径会被拒绝 - 在启用 `*` 的强制阻止模式下,精确域规则、DoH/DoT 端点规则和解析器 IP/CIDR 规则作为例外被支持;后缀允许规则仍需要观察或干运行模式 - 事件存档和导出使用与日志记录相同的结构化事件记录 - 事件导出可以使用自定义信任根、客户端证书和 gzip 压缩批次 ## 构建 ``` go generate ./internal/ebpf go test ./... go build ./cmd/traceguard ``` 常用目标: ``` make generate make test make build make snapshot ``` ## 用法 仅观察: ``` sudo ./traceguard ``` 阻止精确域: ``` sudo ./traceguard -block \ -block-domain example.com \ -block-domain bad.example.org ``` 允许解析器主机名,即使它出现在远程黑名单中: ``` sudo ./traceguard -block \ -blocklist-url https://security.example/blocklist.txt \ -allow-domain resolver.corp.example ``` 拒绝所有 DNS 名称和解析器端点,然后仅允许显式异常: ``` sudo ./traceguard -block \ -block-domain '*' \ -allow-domain corp.example \ -allow-domain 1.1.1.1 \ -allow-domain 1.1.1.0/24 \ -allow-domain https://1.1.1.1/dns-query \ -allow-domain dot://[2606:4700:4700::1111] ``` 如果更喜欢直接使用通配符形式,请用引号将其括起来,以免 shell 展开: ``` sudo ./traceguard -block -block-domain '*' ``` 在不强制执行丢弃的情况下测试策略: ``` sudo ./traceguard -dry-run \ -block-domain '*.example.com' ``` 手动重新加载策略源: ``` sudo kill -HUP $(pidof traceguard) ``` 阻止 DoH 解析器端点和 DoT 解析器端点: ``` sudo ./traceguard -block \ -block-domain https://dns.google/dns-query \ -block-domain dot://one.one.one.one ``` 从带有六小时刷新的远程列表阻止: ``` sudo ./traceguard -block \ -blocklist-url https://security.example/blocklist.txt \ -cache-path /var/lib/traceguard/blocklist.txt \ -refresh-interval 6h ``` 从文件加载手动阻止和允许条目: ``` sudo ./traceguard -block \ -block-domain @/etc/traceguard/block-domains.txt \ -allow-domain @/etc/traceguard/allow-domains.txt ``` 打印程序版本: ``` ./traceguard -v ``` 运行诊断: ``` ./traceguard -doctor ``` 在节点上启用 Kubernetes 丰富: ``` sudo ./traceguard \ -kubernetes-enrich \ -kubernetes-api-url https://kubernetes.default.svc:443 \ -kubernetes-node-name "$(hostname)" ``` 启用 JSON 输出和指标: ``` sudo ./traceguard \ -metrics-addr :9090 ``` 存档事件并本地导出到收集器: ``` sudo ./traceguard \ -event-archive-path /var/lib/traceguard/events.jsonl \ -event-export-url https://siem.example/api/traceguard \ -event-export-auth-token 'Bearer secret-token' \ -event-export-gzip \ -event-export-spool-path /var/lib/traceguard/export-spool ``` 对 HTTPS 事件收集器使用 mTLS: ``` sudo ./traceguard \ -event-export-url https://siem.example/api/traceguard \ -event-export-ca-path /etc/traceguard/siem-ca.crt \ -event-export-client-cert /etc/traceguard/siem-client.crt \ -event-export-client-key /etc/traceguard/siem-client.key ``` 可以使用环境变量代替标志: - `TRACEGUARD_BLOCK` - `TRACEGUARD_DRY_RUN` - `TRACEGUARD_BLOCKLIST_URL` - `TRACEGUARD_BLOCK_DOMAINS` - `TRACEGUARD_ALLOW_DOMAINS` - `TRACEGUARD_CACHE_PATH` - `TRACEGUARD_REFRESH_INTERVAL` - `TRACEGUARD_CGROUP_PATH` - `TRACEGUARD_LOG_PATH` - `TRACEGUARD_LOG_FORMAT` - `TRACEGUARD_METRICS_ADDR` - `TRACEGUARD_EVENT_ARCHIVE_PATH` - `TRACEGUARD_EVENT_EXPORT_URL` - `TRACEGUARD_EVENT_EXPORT_AUTH_HEADER` - `TRACEGUARD_EVENT_EXPORT_AUTH_TOKEN` - `TRACEGUARD_EVENT_EXPORT_BATCH_SIZE` - `TRACEGUARD_EVENT_EXPORT_FL_INTERVAL` - `TRACEGUARD_EVENT_EXPORT_SPOOL_PATH` - `TRACEGUARD_EVENT_EXPORT_CA_PATH` - `TRACEGUARD_EVENT_EXPORT_CLIENT_CERT` - `TRACEGUARD_EVENT_EXPORT_CLIENT_KEY` - `TRACEGUARD_EVENT_EXPORT_GZIP` - `TRACEGUARD_PROCESS_CACHE_TTL` - `TRACEGUARD_KUBERNETES_ENRICH` - `TRACEGUARD_KUBERNETES_API_URL` - `TRACEGUARD_KUBERNETES_TOKEN_PATH` - `TRACEGUARD_KUBERNETES_CA_PATH` - `TRACEGUARD_KUBERNETES_NODE_NAME` - `TRACEGUARD_KUBERNETES_POLL_INTERVAL` 默认情况下,TraceGuard 以 JSON 格式记录日志。使用 `-log-format text` 或 `TRACEGUARD_LOG_FORMAT=text` 切换回文本输出。 手动策略输入: - 使用 `-block-domain` 和 `-allow-domain` 重复添加内联条目 - 使用 `-block-domain @/abs/path` 和 `-allow-domain @/abs/path` 从文件加载条目 - 使用 `TRACEGUARD_BLOCK_DOMAINS` 和 `TRACEGUARD_ALLOW_DOMAINS` 进行基于环境的配置 - 设置 `TRACEGUARD_BLOCK_DOMAINS=@/abs/path` 或 `TRACEGUARD_ALLOW_DOMAINS=@/abs/path` 从文件加载条目 - 使用 `*` 作为拒绝所有标记,配合 `-block-domain '*'` 或 `TRACEGUARD_BLOCK_DOMAINS=*` 示例输出: ``` 2026/03/16 08:17:20 dns level="info" cgroup="/kubepods.slice/kubepods-burstable.slice/pod12345678_1234_1234_1234_123456789abc.slice/cri-containerd-0123.scope" cmdline=["/usr/bin/dig","example.com"] domain="example.com" event="dns" exe="/usr/bin/dig" k8s_app="dns-client" k8s_containers=["app","sidecar"] k8s_images=["ghcr.io/example/app:v1","ghcr.io/example/sidecar:v2"] k8s_namespace="default" k8s_node="worker-1" k8s_owner="dns-client-7f4b6d" k8s_owner_kind="ReplicaSet" k8s_pod="dns-client" k8s_pod_ip="10.0.0.12" k8s_service_account="dns-client" parent_program="bash" pid=31742 pod_uid="12345678-1234-1234-1234-123456789abc" ppid=31680 program="dig" runtime="containerd" service="cri-containerd-0123.scope" transport="udp" uid=1000 2026/03/16 08:17:21 blocked-doh level="info" address="8.8.8.8" container_id="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" endpoint="dns.google" event="resolver_blocked" exe="/usr/bin/curl" parent_program="python3" pid=31811 policy="block" port=443 program="curl" transport="doh" uid=1000 2026/03/16 08:17:22 would-block level="info" cmdline=["/usr/bin/dig","api.example.com"] domain="api.example.com" event="dns" exe="/usr/bin/dig" mode="dry-run" pid=31742 pod_uid="12345678-1234-1234-1234-123456789abc" policy="block" program="dig" runtime="containerd" transport="udp" uid=1000 ``` 示例 JSON 输出: ``` {"timestamp":"2026-03-16T08:17:20.123456Z","level":"info","message":"dns","event":"dns","program":"dig","pid":31742,"exe":"/usr/bin/dig","cmdline":["/usr/bin/dig","example.com"],"uid":1000,"ppid":31680,"parent_program":"bash","cgroup":"/kubepods.slice/kubepods-burstable.slice/pod12345678_1234_1234_1234_123456789abc.slice/cri-containerd-0123.scope","service":"cri-containerd-0123.scope","pod_uid":"12345678-1234-1234-1234-123456789abc","runtime":"containerd","k8s_namespace":"default","k8s_pod":"dns-client","k8s_node":"worker-1","k8s_pod_ip":"10.0.0.12","k8s_service_account":"dns-client","k8s_owner_kind":"ReplicaSet","k8s_owner":"dns-client-7f4b6d","k8s_app":"dns-client","k8s_containers":["app","sidecar"],"k8s_images":["ghcr.io/example/app:v1","ghcr.io/example/sidecar:v2"],"domain":"example.com","transport":"udp"} ``` ## 打包 GoReleaser 已配置为构建 Linux 归档文件以及: - `.deb` - `.rpm` - `archlinux` 运行本地快照发布: ``` goreleaser release --snapshot --clean ``` 生成的软件包安装: - `/usr/bin/traceguard` - `/etc/traceguard/traceguard.env` - `/var/log/traceguard/traceguard.log` 在运行时通过打包的服务默认值 - 系统 systemd unit 到发行版适当的系统路径 - 可选的指标在配置的监听地址上 ## 安全开发注意事项 - 依赖项通过 Go 模块管理,适合 GoReleaser 可验证构建 - 远程黑名单获取仅使用 HTTPS,限制响应大小和网络超时 - 拒绝过大的远程黑名单,而不是静默截断 - 远程黑名单重定向受到限制并必须保持 HTTPS - 日志文件创建拒绝符号链接目标和非普通文件以减少日志路径攻击 - 缓存读取拒绝符号链接,缓存写入是原子的,且缓存的黑名单以受限权限写入 - BPF 解析器使用显式边界检查和固定最大大小以满足验证器并降低解析风险 - 阻止模式在无法发出阻塞事件遥测或无法安全检查 TCP/IPv6 DNS 流量时失败关闭 - 进程丰富在用户空间从 `/proc` 执行;如果进程在丰富前退出,TraceGuard 回退到内核提供的任务元数据 - 进程丰富还从 `/proc//cgroup` 提取 cgroup 路径、可能的服务单元和容器 ID 启发式信息 - 进程丰富现在也从常见的 Kubernetes/容器 cgroup 布局中提取 Pod UID 和运行时提示(如果存在) - 可选的 Kubernetes API 丰富可以添加命名空间、Pod 名称、Pod IP、节点名称、服务账户、控制器工作负载、应用标签、容器名称和图像名称,以观察到的 Pod UID 为键 - `dry-run` 使用与强制模式相同的策略引擎,但记录 `would-block` 决策而不是启用内核丢弃 - `SIGHUP` 触发立即从本地和远程源重新加载策略 - 仅当显式启用 `-metrics-addr` 时才提供指标和运行状况端点 - 事件导出需要 HTTPS 端点,并使用有界内存队列以避免阻塞主事件循环 - 事件导出批次记录为 JSON 数组,支持可配置的身份验证头、可选的 gzip 压缩、可选的 mTLS,并可以将失败的批次溢出到磁盘以供重放 - Kubernetes 丰富使用 HTTPS、承载令牌、限制响应大小和定期缓存刷新,而不是针对每个事件进行实时 API 调用 - 加密的 DoH 和 DoT 流量在解析器端点级别处理;实现不尝试 TLS 拦截或解密
标签:AMSI绕过, DNS安全, DNS监控, Docker镜像, DoH, DoT, Go语言, gzip, HTTPS, Linux内核, mTLS, 事件归档, 健康检查, 允许规则, 后缀匹配, 域名屏蔽, 威胁检测, 安全日志, 日志审计, 流量监控, 程序破解, 系统追踪, 网络安全, 网络流量分析, 网络防护, 自定义请求头, 规则优先级, 证书透明度, 进程元数据, 进程审计, 遥测, 阻断模式, 隐私保护