kuhbs/qubes-snitch

GitHub: kuhbs/qubes-snitch

Qubes OS 上的轻量级交互式防火墙,通过终端提示让用户对虚拟机的网络连接和 DNS 请求逐条审批。

Stars: 0 | Forks: 0

# QUBES-SNITCH Qubes-Snitch 是一个极简的 Qubes OS 网关防火墙管理工具,用于响应来自其自身 VM 背后网络链上的 VM 的连接尝试和 DNS 解析尝试。 它类似于 OpenSnitch 或 Little Snitch,但更为轻量级。它允许你通过一个简单的提示来永久允许或拒绝这些尝试。决策会被存储在 YAML 文件中,并在重启后(当守护进程通过其 systemd 服务启动时)转化为 nftables 规则。也可以手动创建 YAML 文件来预定义允许和拒绝规则。 Qubes-Snitch 旨在替换 Qubes OS 中的默认 sys-firewall VM(例如,你可以将其命名为 sys-snitch)。其优势在于可以在连接尝试发生时交互式地允许或拒绝它们,这使得防火墙的设置比使用 `qvm-firewall` 命令或 Qubes OS 防火墙 GUI 更加简单、快速和安全。因为在后两者中,你必须在连接发生之前就知道并确定要允许哪些连接,这通常需要使用 tcpdump 或 Wireshark。 ## 截图 运行在 `signal-desktop` VM 前端的 `qubes-snitch`: ![Qubes-Snitch running for a signal-desktop VM](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/28e029ba0b005531.png) ## 提示列与颜色 Qubes-Snitch 的颜色仅作为扫描提示。在决定允许或拒绝之前,请务必阅读所有列的内容。 在提示界面下,按 `a` 表示允许,或按 `r` 表示拒绝。`[a/R]` 中的大写字母表示当你按下回车键时的默认选项。 ### 列 - `Q`:待处理的问题数量,使用终端默认颜色 - `SOURCE`:产生流量的 VM,使用该 VM 的 Qubes 标签颜色着色 - `TARGET`:正在连接的目标 IP - `DNS`:解析器名称、查询的名称、DNS 缓存名称、PTR 名称,或 `no PTR` - `SERVICE`:协议/端口或 DNS 查询类型 - `ACTION`:可用的决策按键及可选的输入答案,使用终端默认颜色 ### 颜色规则 - 正常的 IP 流量在 `TARGET` 列保持终端默认颜色,因为仅凭 IP 本身无法判断好坏 - DNS 缓存名称显示为绿色。这意味着 VM 向 DNS 请求了该名称,然后连接到了为该名称返回的 IP - 仅具有 PTR 的名称显示为黄色,并标记为 `PTR name`。这表示 Qubes-Snitch 没有看到来自该 VM 的匹配 DNS 请求,但该 IP 的反向 DNS 返回了一个名称 - 缺失的 DNS/PTR 名称显示为红色,并标记为 `no PTR`。这表示 Qubes-Snitch 无法从 DNS 缓存中获取该 IP 的可读名称,且该 IP 不存在 PTR 记录 - 正常服务使用 `config.yml` 中的 `prompt_protocol_colors` 配置:加密显示为绿色,未加密显示为黄色,未列出显示为红色 - 服务名称和颜色仅作为标签。允许 `https 443/tcp` 即表示允许 TCP 443 端口的流量;Qubes-Snitch 不会验证 TLS、HTTP、QUIC、SNI、证书或应用层的 payload - 发往 Qubes OS 内部解析器的 DNS 流量 (53/udp),其 `TARGET`、`DNS` 和 `SERVICE` 列均显示为蓝色 - 发往任何其他解析器的 DNS 流量 (53/udp),其 `TARGET`、`DNS` 和 `SERVICE` 列均显示为红色,因为这绕过了预期的 Qubes DNS 路径 Qubes OS 的内部 DNS 名称在 Qubes-Snitch 中是硬编码的: - `10.139.1.1` -> `qubes.dns-1.internal` - `10.139.1.2` -> `qubes.dns-2.internal` ### 颜色示例 `signal` 想要通过 `domain 53/udp` 连接到 Qubes OS 内部 DNS 服务器 `10.139.1.1` (`qubes.dns-1.internal`),因此 `TARGET`、`DNS` 和 `SERVICE` 显示为蓝色。 ![Qubes internal DNS resolver 1](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/aa5f5b0080005532.png) `signal` 想要通过 `domain 53/udp` 连接到 Qubes OS 内部 DNS 服务器 `10.139.1.2` (`qubes.dns-2.internal`),因此 `TARGET`、`DNS` 和 `SERVICE` 显示为蓝色。 ![Qubes internal DNS resolver 2](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/6b5e7f5682005534.png) `signal` 想要通过 `domain 53/udp` 连接到外部 DNS 服务器 `8.8.8.8` (`dns.google`),因此 `TARGET`、`DNS` 和 `SERVICE` 显示为红色。 ![External DNS resolver](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/9ed7a843b3005535.png) `signal` 想要向 Qubes OS 内部 DNS 服务器 `10.139.1.1` 查询 `_https._tcp.updates.signal.org`,查询类型为 `DNS SRV`,因此 `TARGET`、`DNS` 和 `SERVICE` 显示为蓝色。 ![DNS SRV query](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/c605106073005536.png) `signal` 想要向 Qubes OS 内部 DNS 服务器 `10.139.1.2` 查询 `updates.signal.org`,查询类型为 `DNS A`,因此 `TARGET`、`DNS` 和 `SERVICE` 显示为蓝色。 ![DNS A query](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/879fb88659005537.png) `signal` 想要连接到 `104.18.2.166`;Qubes-Snitch 找到了与 `updates.signal.org` 匹配的 DNS 缓存,且 `https 443/tcp` 被列为加密服务,因此 `DNS` 和 `SERVICE` 显示为绿色,而 `TARGET` 保持终端默认颜色。 ![DNS cache with encrypted HTTPS service](https://raw.githubusercontent.com/kuhbs/qubes-snitch/main/screenshots/dns-cache-https-encrypted.png) `signal` 想要连接到 `151.101.2.132`;Qubes-Snitch 找到了与 `deb.debian.org` 匹配的 DNS 缓存,但 `http 80/tcp` 被列为未加密,因此 `DNS` 显示为绿色,`SERVICE` 显示为黄色,而 `TARGET` 保持终端默认颜色。 ![DNS cache with unencrypted HTTP service](https://raw.githubusercontent.com/kuhbs/qubes-snitch/main/screenshots/dns-cache-http-unencrypted.png) `signal` 想要连接到 `203.0.113.10`;Qubes-Snitch 仅有 PTR 名称 `mail.example.com`,因此 `DNS` 显示为黄色。`imaps 993/tcp` 被列为加密服务,因此 `SERVICE` 显示为绿色,而 `TARGET` 保持终端默认颜色。 ![PTR with encrypted IMAPS service](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/5c24602845005638.png) `signal` 想要连接到 `193.174.160.18`;Qubes-Snitch 没有匹配的 DNS 缓存,也没有 PTR 记录,因此 `DNS` 显示为红色的 `no PTR`。`https 443/tcp` 被列为加密服务,因此 `SERVICE` 显示为绿色,而 `TARGET` 保持终端默认颜色。 ![No PTR with encrypted HTTPS service](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/a66f22334d005640.png) `signal` 想要连接到 `198.51.100.23`;Qubes-Snitch 没有匹配的 DNS 缓存,也没有 PTR 记录,且 `telnet 23/tcp` 未在 `prompt_protocol_colors` 中列出,因此 `DNS` 和 `SERVICE` 显示为红色,而 `TARGET` 保持终端默认颜色。 ![No PTR with unlisted telnet service](https://raw.githubusercontent.com/kuhbs/qubes-snitch/main/screenshots/no-ptr-telnet-unlisted.png) `signal` 想要连接到 `198.51.100.77`;Qubes-Snitch 没有匹配的 DNS 缓存,也没有 PTR 记录,且 `31337/tcp` 不在 `/etc/services` 或 `prompt_protocol_colors` 中,因此 `DNS` 和 `SERVICE` 显示为红色,而 `TARGET` 保持终端默认颜色。 ![No PTR with unknown port](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/3c060f2bb9005711.png) ## 安装说明 在 dom0 中运行以下命令,通过 DisposableVM 下载安装程序: ``` # 下载 qubes-snitch dom0 installer 并将其复制到 dom0 qvm-run --dispvm --pass-io --no-gui --user user -- \ 'curl -fsSL https://raw.githubusercontent.com/kuhbs/qubes-snitch/main/install-dom0.sh' \ > install-dom0.sh # 首先阅读 dom0 installer 源代码 less install-dom0.sh ``` dom0 安装程序会将当前的存储库克隆到 `tpl-qubes-snitch` 中,并以 root 身份在该检出版本中运行 `install.sh`。如果你需要进行严格的供应链审查,请务必审查或固定克隆下来的存储库内容,而不仅仅是这个包装脚本。 安装程序会创建 `tpl-qubes-snitch` 和 `sys-snitch`,将 Qubes-Snitch 安装到模板中,安装 dom0 的 `qubes.SnitchSources` qrexec 服务,并启用所需的 Qubes ProxyVM 服务: ``` # 为 installer 添加可执行权限 chmod 700 install-dom0.sh # 运行 installer ./install-dom0.sh ``` qvm-services 中的 `qubes-network` 和 `qubes-firewall` 之所以为 sys-snitch 启用,是因为 Qubes 负责维护安全的 ProxyVM 底层管道(使网络流量更安全)。Qubes-Snitch 在此基础上添加了交互式的允许/拒绝策略层。 安装程序完成后,为 `sys-snitch` 选择上游网络,启动它,并首先将一个 VM 路由到该网络: ``` # 将 sys-snitch “连接到互联网” qvm-prefs sys-snitch netvm sys-net # 启动 sys-snitch qvm-start sys-snitch # 将 VM 附加到 sys-snitch 之后 qvm-prefs netvm sys-snitch ``` ## 首次使用 启动 `sys-snitch`,并在以 `sys-snitch` 作为其 NetVM 的 VM 中使用会产生互联网流量的工具和程序。`qubes-snitchd` systemd 服务会一直在 `sys-snitch` 中运行。当出现之前未创建过规则的流量时,它会从 `sys-snitch` 发送一个桌面通知。 该通知仅告知你有流量正在等待决策。要允许或拒绝它,请以普通用户身份(无需 root 权限)在 `sys-snitch` 中打开终端并运行: ``` qubes-snitch ``` ## 保存的决策 对于普通 VM 和特定用途的 DispVM 基础镜像,决策是持久化的。Qubes-Snitch 会将允许/拒绝规则写入 `/rw/usrlocal/qubes-snitch/rules/.yml`,重新加载 nftables,并且对于已保存的相同决策不会再次询问。要移除已保存的规则,你必须手动编辑匹配的 YAML 文件,然后以 root 身份在 `sys-snitch` 中运行以下命令: ``` systemctl restart qubes-snitchd.service ``` 当检测到常规的 UDP DNS 查询时,DNS 决策会按域名进行显示。与 DNS 服务器本身的连接也是一个网络决策,因此 DNS 可能会询问两次:一次用于解析器连接,另一次用于域名。DNS-over-TCP 仅是一个常规的 TCP/53 网络决策;如果你不希望客户端使用它,请拒绝 TCP/53。 当一个 DNS 域名问题尚未保存规则时,Qubes-Snitch 会将提示加入队列,并丢弃该数据包,而不是回答 DNS `REFUSED`。客户端会将其视为普通的 UDP 丢包。如果你在客户端重试之前允许了该域名,重试的请求就会到达解析器。对于允许的 A 规则,Qubes-Snitch 还会执行自己的解析器查找,以填充基于 TTL 的显示缓存。 ``` Q SOURCE TARGET DNS SERVICE ACTION 2 browser 10.139.1.1 qubes.dns-1.internal domain 53/udp [a/R] a 1 browser 10.139.1.1 example.org DNS A [a/R] a ``` DNS 非常复杂:该协议有许多记录类型、类别、操作码、可选部分、DNSSEC 记录、EDNS、不常见的兼容性行为,以及来自故障软件的畸形数据包。Qubes-Snitch 刻意不试图成为一个全功能的 DNS 防火墙。它使用一小部分受支持的客户端问题白名单,并在其他所有问题变成策略之前予以拒绝。 受支持的 DNS 问题必须是 UDP/53,不超过 1232 字节,操作码为 QUERY,类别为 IN,有且仅有一个问题,没有回答/授权/真正的附加部分,EDNS 最多包含一个 OPT 伪记录,使用常规的 ASCII 域名,以及以下确切的 qtype 名称之一:A, CNAME, MX, TXT, SRV, PTR, CAA, NS, SOA, HTTPS, SVCB, NAPTR, DS, DNSKEY, RRSIG, NSEC, NSEC3。一个 EDNS OPT 记录可以包含多个 EDNS 选项。实时的 AAAA 查询会在没有提示的情况下直接被拒绝 (REFUSED),因为 Qubes-Snitch 仅支持 IPv4。ANY、AXFR、IXFR、非 IN 类别、未知的 qtype、诸如 `TYPE1` 的通用 qtype 别名、数字 qtype、无效名称、过大的 DNS 正文体、多问题数据包、多个 EDNS OPT 记录以及格式错误的 DNS 都会在没有 YAML 规则的情况下被直接拒绝。 对于没有规则的新的受支持 DNS 问题,Qubes-Snitch 会使用 NFQUEUE drop 判决拒绝当前排队的数据包,将提示加入队列,并在规则保存后让客户端自然重试。它不会合成成功的 DNS 回答。解析器的回复不会被解析。在 A 规则被允许后,Qubes-Snitch 会通过 `/etc/resolv.conf` 执行自己的 A 记录查找,并将结果作为仅存在于 RAM 显示提示进行存储,其键值为源 VM 和回答的 IP。 此缓存仅用作后续网络提示的显示提示。如果 `browser` 允许了 `turn.cloudflare.com A`,并且 Qubes-Snitch 自己的查找返回了 `141.101.90.1`,那么后续对 `141.101.90.1` 的连接就可以在 `DNS` 列中显示 `turn.cloudflare.com`。 已保存的规则仍然使用 IP 地址、协议和端口。Qubes-Snitch 绝不会将主机名写入 nftables 的匹配规则中: ``` rules4: - ptr: turn.cloudflare.com dest: 141.101.90.1 proto: tcp port: "443" action: allow ``` DNS 提示缓存按源 VM 划分作用域,因此一个 VM 的 DNS 查找不会标记另一个 VM 的流量。它不用于自动允许流量或匹配规则。如果缓存中没有针对目标 IP 的有效 TTL 最新答案,且不存在 PTR 记录,`DNS` 列将回退显示为 `no PTR`。 ## 手动创建防火墙规则 规则位于提供网络的 AppVM/ProxyVM `sys-snitch:/rw/usrlocal/qubes-snitch/rules/` 中。每个源都有以其名称命名的专属文件,例如 `sys-snitch:/rw/usrlocal/qubes-snitch/rules/browser.yml`。 规则检查从上到下进行。请将具体的拒绝规则放在宽泛的允许规则之前。 `ptr` 是一个人类可读的注释。Qubes-Snitch 在创建规则时会将已知的目标名称写入其中,但匹配操作只使用 `dest`、`proto` 和 `port`。 对于所有目标或所有端口,请使用 `any`。目标也可以是一个 IP 或一个 CIDR 网络。TCP/UDP 端口必须是用引号括起来的字符串:像 `"443"` 这样的数字,像 `"8000-8999"` 这样的数字范围,`"any"`,或者 `/etc/services` 中的名称,包括像 `"domain-s"` 这样带连字符的名称。协议必须写成名称形式:`tcp`、`udp` 或 `icmp`。像 `"6"`、`"17"`、`"06"` 或 `"99"` 这样的数字协议字符串将被拒绝。 ICMP 规则目前仅将 ICMP 作为协议进行匹配。Qubes-Snitch 不区分 ICMP 类型/代码,因此允许 ICMP 即表示允许所有发往匹配目标的 ICMP。 要拒绝以下任何内容,请将 `allow` 更改为 `reject`。规则语言只有 `allow` 或 `reject`;它没有单独的静默拒绝操作。 示例: ``` rules4: # One IPv4 address and one named port from /etc/services - ptr: any dest: 93.184.216.34 proto: tcp port: "https" action: allow # One IPv4 subnet and one numeric port - ptr: any dest: 93.184.216.0/24 proto: tcp port: "443" action: allow # One IPv4 address and one numeric port range - ptr: any dest: 93.184.216.34 proto: tcp port: "8000-8999" action: allow ``` 要允许所有 TCP、UDP 和 ICMP IPv4 流量: ``` rules4: # Allow all TCP traffic - ptr: any dest: any proto: tcp port: "any" action: allow # Allow all UDP traffic - ptr: any dest: any proto: udp port: "any" action: allow # Allow all ICMP traffic - ptr: any dest: any proto: icmp port: "any" action: allow ``` 要允许 DNS 查询,请在 `dns` 下添加条目。使用常规的 DNS 名称进行精确匹配。在 DNS 名称前加上 `*.` 前缀以匹配该后缀下的子域。`"*.example.org"` 可以匹配 `www.example.org`,但不能匹配裸顶级域 `example.org`;如果你也想包含顶级域,请添加一条单独的 `example.org` 规则。手动配置的 DNS 规则只能使用上述受支持的确切 qtype 名称;诸如 `TYPE1` 的别名、数字 qtype 和 `AAAA` 将被拒绝。Qubes-Snitch 不支持 IPv6 流量,因此实时的 DNS `AAAA` 查询会收到 DNS `REFUSED` 响应,且手动配置的 DNS 规则不得使用 `AAAA`: ``` dns: # One normal domain - qname: example.org qtype: A action: allow # One wildcard domain suffix - qname: "*.example.org" qtype: A action: allow # Mail lookup - qname: example.org qtype: MX action: allow # SIP/service discovery - qname: _sip._udp.example.org qtype: SRV action: allow ``` 请注意,这两个相关部分(`rules4` 和 `dns`)都必须被定义,否则 `qubes-snitchd` 守护进程将因配置错误而中止。如果你不使用某个部分,请将其定义为空列表 (`[]`)。 YAML 具有特殊的未加引号的标量值。像 `no`、`yes`、`true`、`false`、`null` 这样的词以及数字可能会被解析为布尔值、null 或整数,而不是字符串。请务必给 DNS 名称、通配符规则以及每一个 `port` 加上引号,例如 `qname: "*.example.org"` 和 `port: "443"`。Qubes-Snitch 会严格校验加载的 YAML,并在遇到错误的策略时直接中止,而不会进行隐式的转换。 Qubes-Snitch 仅支持 IPv4 规则部分。不支持额外的地址族规则部分。 ## 查看被拒绝的数据包和 DNS 查询 要以实时方式查看所有 Qubes-Snitch 的拒绝日志(包括被拒绝的数据包和被拒绝的 DNS 查询),请在 `sys-snitch` 中以 root 身份运行以下命令: ``` journalctl -f -g QUBES-SNITCH ``` 仅查看 nftables/内核数据包拒绝日志: ``` journalctl -k -f -g QUBES-SNITCH ``` 数据包拒绝日志由 nftables 发出,如下所示: ``` # 被拒绝的发往 1.2.3.4 的 ICMP packet Jun 14 19:52:50 sys-snitch kernel: QUBES-SNITCH browser reject IN=vif17.0 OUT=eth0 MAC=fe:ff:ff:ff:ff:ff:00:16:3e:5e:6c:00:08:00 SRC=10.137.0.42 DST=1.2.3.4 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=636 DF PROTO=ICMP TYPE=8 CODE=0 ID=50804 SEQ=6 # 被拒绝的 HTTPS packet Jun 14 19:54:48 sys-snitch kernel: QUBES-SNITCH browser reject IN=vif17.0 OUT=eth0 MAC=fe:ff:ff:ff:ff:ff:00:16:3e:5e:6c:00:08:00 SRC=10.137.0.42 DST=162.55.47.18 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=26953 DF PROTO=TCP SPT=48820 DPT=443 WINDOW=64240 RES=0x00 SYN URGP=0 ``` 被拒绝的 DNS 查询由 `qubes-snitchd` 记录,因为 DNS 拒绝是在本地进行回答的,而不是由 nftables 拒绝的。它们如下所示: ``` Jun 14 20:12:34 sys-snitch qubes-snitchd.py: QUBES-SNITCH browser reject DNS SRC=10.137.0.42 DST=10.139.1.1 QTYPE=A QNAME=kuhbs.com ``` ## 故障排除 从 dom0 打开提示 UI: ``` qvm-run sys-snitch 'xfce4-terminal --command /usr/bin/qubes-snitch' ``` 在 `sys-snitch` 中检查守护进程状态和日志: ``` systemctl status qubes-snitchd.service journalctl -u qubes-snitchd.service ``` 实时查看被拒绝的数据包和 DNS 查询: ``` journalctl -f -g QUBES-SNITCH ``` 手动编辑 YAML 规则后,在 `sys-snitch` 中重启守护进程: ``` systemctl restart qubes-snitchd.service ``` ## 重要的安全注意事项 Qubes-Snitch 是一个防火墙管理系统,因此其安全模型在那些如果采用更宽松的策略会有利于用户体验的地方,特意设计得非常严格。它只会在能够明确识别源 VM 并能安全描述数据包的情况下,才向用户询问流量决策。可疑、格式错误或无法追溯来源的流量将被记录,通过 `notify-send -u critical --expire-time=0` 进行报告,并被拒绝。 Qubes-Snitch 在面向用户的防火墙决策中使用“拒绝”一词:即不允许该流量,也不创建允许规则。已知的 nftables 拒绝规则使用真正的内核拒绝机制(`reject with icmpx admin-prohibited`)。直接从 NFQUEUE 拒绝的数据包会使用 NFQUEUE 的 `drop` 判决进行丢弃,因为 Python 的 NFQUEUE API 仅提供 `accept()` 和 `drop()`,而没有安全的 `reject()` 辅助方法。Qubes-Snitch 不会在 Python 中合成 ICMP/TCP 拒绝数据包,因为那会增加脆弱的数据包构建代码和额外的攻击面。 ### 源 VM 身份 最重要的安全输入是源 VM 的身份。数据包只包含源 IP 地址,而不包含 Qube 的名称。因此,Qubes-Snitch 会通过 `qubes.SnitchSources` qrexec 服务向 dom0 请求实时的 `VM name | IP address | label color | VM class | template` 映射。该 qrexec 服务包含每一行具有 IP 地址的 VM,包括已暂停的 VM,因此策略身份在暂停的 VM 因获得焦点而恢复之前就已经准备好了。未编号的通用默认 DispVM 行会被忽略,因此像 `sys-firewall` 或 `sys-usb` 这样的提供者行不会成为持久的策略源。 如果数据包来自未知的源 IP,Qubes-Snitch 会拒绝该数据包,并强制从 dom0 进行一次全新的源查找。如果刷新后该 IP 仍然未知,则源身份将被视为已损坏。这在健康的 Qubes 网关中是不应该发生的。守护进程会输出一条 `QUBES-SNITCH SECURITY ...` 日志,通过 `notify-send -u critical --expire-time=0` 进行报告,然后退出,以便 systemd 将其标记为失败,并保持故障关闭的 nftables 表生效。Qubes-Snitch 不会为原始未知的源 IP 创建提示或规则文件,因为那会允许用户为无法安全归因于某个 Qube 的流量保存策略决策。 ### 故障关闭式启动 Systemd 负责维护故障关闭设置。该服务会在 Python 启动之前,使用 `ExecStartPre` 加载 `/usr/lib/qubes-snitch/fail-closed.nft`。当守护进程停止或失败时,它还会通过 `ExecStopPost` 加载相同的故障关闭表。守护进程不会在启动或关闭期间尝试安装故障关闭规则;它期望 systemd 已经完成了这些操作。 守护进程服务依赖于 Qubes 的 `qubes-network.service` 和 `qubes-firewall.service`,以便在 Qubes-Snitch 添加其交互式策略层之前,由 Qubes 负责处理正常的 ProxyVM 底层管道和防欺骗设置。 在启动时,`qubes-snitchd` 会加载本地配置和规则,向 dom0 请求源身份,然后用渲染好的基于源的允许/拒绝策略替换掉故障关闭表。如果配置、规则、qrexec、源身份或 nft 重新加载失败,守护进程将退出,而 systemd 将保持加载故障关闭表,从而使转发的流量保持被阻止状态,而不是悄无声息地放行。 故障关闭的 nftables 表意味着,在 `qubes-snitchd` 掌握了足够的信息来做出交互式决策之前,nftables 已经默认阻止了未知的转发流量。这可以防止启动或 qrexec 故障演变成临时的全部允许窗口。 ### DNS 决策与 DNS 显示提示 DNS 在 Qubes-Snitch 中有两个独立的工作: - 到 DNS 解析器的 UDP 连接是正常的网络流量,可以像任何其他 UDP 流量一样被允许或拒绝 - 一个正常的 UDP DNS 问题也可以成为一个域决策,例如 `browser DNS A example.org` 转发的 TCP/UDP 数据包仅包含 IP 地址。DNS 决策是针对传出 UDP/53 查询的 qname/qtype 策略。Qubes-Snitch 不会解析解析器的回复。在允许了 A 域名决策后,Qubes-Snitch 会通过 `/etc/resolv.conf` 执行自己正常的解析器查找,并将答案作为仅存于 RAM 中的、从 IP 到主机名的显示提示进行存储。例如,如果 `browser` 允许了 `turn.cloudflare.com A`,并且 Qubes-Snitch 将其解析为 `141.101.90.1`,那么后续对 `141.101.90.1` 的连接就可以在 `DNS` 列中显示 `turn.cloudflare.com`。 此 DNS 缓存仅用作显示提示。它不会自动允许流量,而已保存的 nftables 规则仍然会匹配具体的 IP 地址、协议和端口。该缓存按源 VM 划分作用域,并在 Qubes-Snitch 自己查找返回的 DNS 回答 TTL 到期时过期。 传出 UDP/53 会在宽泛的 `ct state established,related accept` 规则之前加入队列。这可以防止重用的 UDP 解析器 socket 绕过 qname/qtype 策略。DNS 回复仍被视为已建立的流量,并且不会被解析。 Qubes-Snitch 仅将 DNS qname/qtype 策略应用于明文的 UDP/53 DNS。DNS-over-TCP、DNS-over-HTTPS、DNS-over-QUIC 以及任何其他加密或非 UDP/53 的 DNS 方法都被视为正常的网络流,如果不希望其通过,则必须作为流量予以拒绝。 ### 格式错误的 DNS 和数据包 正常的 DNS QUERY 数据包有且仅有一个问题,并且必须适合 1232 字节的 UDP DNS 正文体。多问题或过大的 DNS 查询可能会隐藏意想不到的解析器攻击面,并且不会被正常的客户端提示流量所使用。Qubes-Snitch 会记录、通知并拒绝它们,而不是合成一个 DNS `FORMERR` 回复。 实时的 VM DNS 问题必须使用正常的 ASCII 域名,例如 `example.com` 或 `www.example.com`。SRV 查询可以使用正常的 `_service._proto.example.com` 形式,例如 `_sip._udp.example.com`。像 `*.example.com` 这样的字面通配符名称、DNS 根 `.`、单标签名称、转义的线路名称、无效标签、`xn--` punycode/IDN 名称以及外观相似/诈骗式的 Unicode 域名,在它们成为 YAML 策略之前就会被拒绝。手动配置的 YAML 仍然可以包含像 `"*.example.com"` 这样的通配符 DNS 规则。 对于报头格式错误、完全无法解析的数据包、端口 0、IPv4/UDP 声明长度不一致、TCP 数据偏移错误以及其他无法安全转化为提示的数据包数据,将被记录、通知并拒绝。它们永远不会创建持久的允许/拒绝规则。 ### 满负荷的提示队列 提示队列是有界限的,因此嘈杂的 VM 不会使守护进程的内存无限增长。如果队列已满,Qubes-Snitch 将保留现有的提示,拒绝新的独特提示,并在日志中以正常的速率限制记录被拒绝的流量。它不会针对队列已满时的拒绝发送桌面通知。 ### 针对正常拒绝的合成 DNS 回复 对于用户正常拒绝的 DNS 域名,Qubes-Snitch 会合成一个本地的 DNS `REFUSED` 响应。这会告知客户端该查询因策略而被拒绝,从而使客户端能够快速失败,而不是等待 DNS 超时和重试。此行为仅用于已保存的拒绝决策,而不适用于未回答的提示。一个新的未回答 DNS 问题会被加入队列等待用户处理,而当前的数据包会使用 NFQUEUE `drop` 判决被拒绝,且不会 DNS `REFUSED` 回复,因此客户端会看到普通的 UDP 丢包,并可以在用户允许后重试。格式错误的 DNS 也会使用 NFQUEUE `drop` 判决进行拒绝。 ## 禁止执行的操作 - 不要使用 `qvm-firewall` 或 Qubes 防火墙 GUI 来管理位于 Qubes-Snitch 后面的 VM - 不要在 VM 名称中的任何位置使用 `unknown`;Qubes-Snitch 保留 `unknown` 作为内部源哨兵 - 不要在非 DispVM 名称中的任何位置使用 `disp`;`disp*` 名称是为 Qubes 一次性 VM 及其临时策略文件保留的 - 不要将 VM 命名为 `dispvm-*`;Qubes-Snitch 保留了该前缀,用于共享的 DispVM 策略文件 - 不要创建 `dispvm-default-dvm.yml` 或 `dispvm-.yml`;通用的默认 DispVM 没有稳定的特定用途策略 - 不要手动编辑带编号的 `disp1234.yml` 文件;它们是临时的,当带编号的 DispVM 消失或 `qubes-snitchd` 停止/重启时,它们会被删除 - 如果你重命名了特定用途的 DispVM 基础镜像,请手动清理旧的 `dispvm-.yml` 文件 ## VM 名称与标签 Qubes-Snitch 通过数据包的源 IP 来识别源 VM,然后使用实时的 dom0 qrexec 查找将该 IP 转换为 VM 名称和 Qubes 标签颜色。这也适用于新的 VM 和 DispVM,因为守护进程会监视 QubesDB,寻找与 Qubes 自身防火墙所使用的相同的 connected-IP 变更,然后从 dom0 刷新内存中的映射表。 Qubes 默认的 `sys-firewall` 使用以 IP 为键的 QubesDB 数据,例如 `/qubes-firewall/` 和 `/connected-ips`。这对于 Qubes 自身的防火墙规则来说已经足够了,但它并不能为本 UI 提供可读的交互式提示所需的 VM 名称和标签颜色。Qubes-Snitch 仅将这些 QubesDB 路径用作推送信号,然后向 dom0 请求当前的 VM-name/IP/label 映射。 如果查找由于某些意外原因失败,或者如果数据包来自在强制从 dom0 刷新后仍然未知的 IP,Qubes-Snitch 会将源身份视为已损坏。守护进程会通过 `notify-send -u critical --expire-time=0` 进行报告并退出,以便 systemd 将其标记为失败,并保持故障关闭的 nftables 规则继续生效。 经过测试后,你可以按照相同的方式将更多的 VM 路由到其中。建议你逐个连接你的 VM,这样你就不会被新连接确认的提示所淹没。 当位于 `sys-snitch` 后面的 VM 进行新的连接尝试时,`sys-snitch` 会显示桌面通知。从 XFCE 启动器中打开 Qubes Snitch,或者在 dom0 中运行以下命令: ``` qvm-run sys-snitch 'xfce4-terminal --command /usr/bin/qubes-snitch' ``` 然后允许或拒绝排队等待的连接。 ## 运行测试 从存储库根目录运行测试套件: ``` python3 -m unittest discover -s tests -p 'test_*.py' -v ``` ## 作者信息 Blunix GmbH 柏林 `root@Linux:~# Support | Consulting | Hosting | Training` Blunix GmbH 提供 7x24x365 的 Linux 紧急支持和咨询服务,提供使用 Ansible 配置管理的 Debian Linux 托管服务等级协议,以及 Linux 培训和研讨会。 在 https://www.blunix.com 了解更多。 该软件可以独立使用,同时也集成到了 https://kuhbs.com 中,这是一个专为终端用户设计 Qubes OS 配置管理系统。 ## 许可证 Apache-2.0 请参阅本存储库根目录下的 `LICENSE` 文件。
标签:nftables, Qubes OS, 流量监控, 系统运维, 网络安全, 防火墙, 隐私保护