工程文章

云端 Mac CI 出站连接审计:建立可复查的通信基线

云端 Mac CI 出站连接审计:建立可复查的通信基线

一条原本稳定的 CI 流水线突然开始访问陌生地址,先不要立刻封禁。构建工具、依赖解析器、测试进程和后台更新任务都可能产生出站连接。有效的排查方式,是选择一次可重复的构建,在限定时间内同时记录“哪个进程在运行、连接去了哪里、连接何时出现”,再把结果整理成基线。

先固定一次可比较的采样窗口

选择依赖已安装、工作区干净且没有其他团队任务的时段。记录提交号、构建命令、macOS 版本、Xcode 版本和开始时间。首次采样只运行一个任务,避免两个流水线共享进程后无法归因。

建议准备两组数据:一组执行最小构建,另一组执行完整测试。前者暴露依赖解析和编译连接,后者还会覆盖模拟器、测试服务与产物上传。每组重复两次,第二次通常能区分首次下载与稳定构建。

出站基线不是一张永久有效的 IP 清单,而是一份“已知任务在已知阶段为何访问某类服务”的证据。

用三层证据观察连接

进程与现存连接

lsof 适合回答采样瞬间谁仍保持连接。使用数字地址可避免反向解析引入额外 DNS 流量。

sudo lsof -nP -iTCP -sTCP:ESTABLISHED \
  | awk 'NR == 1 || $1 ~ /xcode|swift|git|ruby|node|curl/'

不要只按进程名过滤并丢弃原始结果。先保存全量快照,再生成便于阅读的子集。构建脚本可能通过系统工具发起下载,名字未必包含工程关键字。

流量与短连接

nettop 提供按进程汇总的实时视角,tcpdump 则能保留几十毫秒内结束的 TCP 握手。远程执行时只抓出站 SYN,不采集负载,可减少日志中的业务数据。

nettop -P -L 1
sudo tcpdump -i any -nn -l \
  'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'

抓包前确认当前 SSH 会话的远端地址。过滤条件写错可能制造大量噪声,但观察命令本身不会改变防火墙规则。

生成一份可归档的采集包

下面的脚本保存系统信息、路由、连接快照和 60 秒握手记录。执行前把构建准备到即将开始的状态;脚本启动后,在另一个会话运行构建。

#!/bin/zsh
set -euo pipefail

OUT="${1:-$HOME/ci-egress-$(date -u +%Y%m%dT%H%M%SZ)}"
mkdir -p "$OUT"

date -u > "$OUT/time.txt"
sw_vers > "$OUT/system.txt"
xcodebuild -version > "$OUT/xcode.txt"
route -n get default > "$OUT/default-route.txt"
scutil --dns > "$OUT/dns.txt"
sudo lsof -nP -i > "$OUT/lsof-before.txt"

sudo tcpdump -i any -nn \
  'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0' \
  -w "$OUT/outbound-syn.pcap" &
CAPTURE_PID=$!

sleep 60
sudo kill -INT "$CAPTURE_PID"
wait "$CAPTURE_PID" || true

sudo lsof -nP -i > "$OUT/lsof-after.txt"
nettop -P -L 1 > "$OUT/nettop.txt"
printf '%s
' "$OUT"

如果构建超过 60 秒,应按“解析依赖、编译、测试、归档”分别采样,而不是无限延长一次抓包。分阶段结果更容易定位新增连接来自哪一步。

把目标整理成通信基线

不要看到陌生 IP 就判断异常。先结合进程、端口、出现阶段和工程配置归类。动态分发网络可能频繁更换地址,因此基线应以用途和可验证域名为主,IP 只保留为当次证据。

| 类别 | 应记录的证据 | 处理方式 | |---|---|---| | 源码与依赖 | 进程、域名、构建阶段 | 对照锁文件与仓库配置 | | 测试服务 | 测试用例、端口、持续时间 | 确认是否为预期集成测试 | | 产物传输 | 上传脚本、目标、文件类型 | 检查是否只在归档阶段出现 | | 系统后台流量 | 系统进程、触发时间 | 与构建流量分开标记 | | 未知连接 | 完整命令行、父进程、抓包时间 | 暂停合并并复现取证 |

对未知进程继续执行 ps -o pid,ppid,user,start,command -p PID,再检查父进程。若连接只出现一次,可将抓包时间与 CI 日志时间统一为 UTC 后对齐,而不是依赖人工回忆。

常见误判与安全边界

最常见的误判是把动态 IP 当稳定身份。第二个问题是只采集构建结束后的 lsof,此时短连接已经消失。第三个问题是在远程节点上直接启用拒绝规则;规则未经本地控制台验证,可能同时切断 SSH、DNS 或依赖下载。

更稳妥的顺序是观察、归类、复现、评审,最后才实施限制。确需收紧出口时,先保留现有管理连接和 DNS 路径,准备回滚命令,并在单个非关键任务上验证。防火墙规则、代理配置和构建脚本必须进入版本管理,不能只留在节点本地。

将基线接入日常检查

每次工具链、依赖源或上传流程变更后重跑同一采样。比较时关注新增的“进程—目标—阶段”组合,而不是比较 IP 行数。允许项应写清负责人、用途和复查条件;无法解释的连接不能靠备注永久放行。

最终保留采集脚本、原始文件、分类表和对应提交号。这样遇到异常下载、构建变慢或凭据疑似外发时,团队能从真实差异开始排查,而不是重新猜测正常流量长什么样。

常见问题

可以直接把采集到的目标 IP 写成防火墙白名单吗?

不建议。依赖下载和托管服务常使用动态地址或共享网络,应先按服务用途和域名归类,再结合稳定出口或代理策略实施限制。

为什么 lsof 与 tcpdump 记录到的连接数量不同?

lsof 只反映执行瞬间仍存在的连接,tcpdump 可以记录采样窗口内出现过的握手;短连接通常只会留在抓包结果中。

OrbVPS 云端 Mac

在独享 Apple Silicon 物理节点上运行构建

按机型、区域和租用周期配置节点,实际可用状态以控制台实时返回为准。

立即租用云端 Mac