客户说日志不能发到外部
目标
制作一个让客户负责人在客户服务器上亲自运行的支持包采集器。按白名单收集、对机密脱敏、设置 manifest 和大小上限,并打成相同输入得到相同字节的 tar.gz。
为什么重要
要从不能把日志发往外部的客户那里拿到包,必须得到客户安全负责人的批准。如果说不清放进了什么、脱敏了什么以及多少处、收到的文件是不是审查过的那个文件,这个包就出不去。每次都由人挑选文件,机密会泄漏,或者打出没有用的包。
材料:/opt/lab/p1a-bundle/site —— 客户服务器副本(VERSION、etc、logs、run/environ、data)。/opt/lab/p1a-bundle/rules.tsv —— 客户安全团队追加的脱敏规则。 工作目录是 /root/bundle。预计 60 分钟,会话结束后 /root/bundle 会消失。
收集规则(本实验的约定):
- 放入的内容:
VERSION、etc/下的普通文件(不含符号链接)、logs/正下方的*.log、run/environ(在 stage 中为env.txt,把 NUL 换成换行并按名称排序)。 - 脱敏的内容:整个私钥块 →
[REDACTED:private_key],Bearer之后的值 →[REDACTED:token],键名以 password、passwd、secret 结尾的值 →[REDACTED:password],以 token、api_key 结尾的值 →[REDACTED:token],邮箱 →[REDACTED:email]。键和值以:或=分隔。键名和分隔符保留。 - 日志上限:每个文件 65536 字节。在脱敏之后,只保留靠近末尾一侧的完整行。
步骤
- 把材料 site 中符合收集规则的文件的相对路径,每行一个写入 /root/bundle/scope.txt。
- 制作 /root/bundle/redact.py:对标准输入脱敏后输出到标准输出,并向标准错误打印
redactions=N。 - 制作 /root/bundle/collect.py:
python3 collect.py SRC OUT把白名单文件脱敏后放入 OUT/stage。 - 让 collect.py 生成 OUT/stage/manifest.json(files 数组:path、size、sha256、redactions、truncated,按 path 排序,total_redactions)。
- 对日志文件应用 65536 字节的上限,并让 manifest 的 truncated、redactions 与实际相符。
- 让 collect.py 生成 OUT/support-bundle.tar.gz。条目在
support-bundle/之下按名称排序,相同输入无论何时何地打包,sha256 都必须相同。 - 让它接收
--rules FILE,把kind<TAB>정규식(占位符为正则表达式)规则应用在默认规则之后,并用[REDACTED:kind]脱敏。 - 用材料 site 和 rules.tsv 生成 /root/bundle/delivery/support-bundle.tar.gz、SHA256SUMS、REDACTION-REPORT.tsv。
参考
- 评分器会用每次重新埋入机密的服务器树,亲自运行你的 redact.py 和 collect.py。针对材料中的值定制规则或写死的做法都会不合格。
- 过度脱敏也是失败:
password_min_length: 12、token_ttl_sec: 3600、passwordless_login: false、orders-api@sha256:…这样的行必须原样保留。 - 从第 3 步开始,一直在 collect.py 这一个文件上扩展。删掉前面步骤的功能,前面步骤的评分会再次不合格。
- 查看以 NUL 分隔的文件:
tr '\0' '\n' < /opt/lab/p1a-bundle/site/run/environ - 确认确定性:把同一条命令运行两次(中间用
touch改变原件的 mtime),并比较sha256sum。 - 常见错误:原样使用
tarfile.open(..., "w:gz"),在 manifest 中放入生成时间,截断之后再脱敏。
用白名单决定放入什么
把材料 site 中符合收集规则的文件的相对路径,每行一个写入 /root/bundle/scope.txt。
先用 find 浏览一遍整个 site。只放入规则里按名称写明的文件,已轮转的日志、子目录中的日志、客户数据、pid 文件不在规则里,所以会被排除。路径是相对于 site 的相对路径。
窄而准的脱敏过滤器
制作 /root/bundle/redact.py:对标准输入脱敏后输出到标准输出,并向标准错误打印 redactions=N。
正则表达式按形态来定。私钥是多行的,所以要先于逐行规则,用 re.S 整块替换。键名必须是以 password“结尾”,而不是“含有”password,password_min_length 才能保留下来。值可能被引号包住,也可能在 & 或空白处结束。
只收集白名单文件并生成脱敏后的 stage
制作 /root/bundle/collect.py:python3 /root/bundle/collect.py SRC OUT 把白名单文件脱敏后放入 OUT/stage。
在同一目录中 import redact.py,就不用把规则写两遍。遍历 etc 下时,用 os.path.islink 过滤链接;run/environ 用 NUL 拆分、排序后存为 env.txt。重新运行时,不要让上一次 stage 的文件残留下来。
供审查人员核对的 manifest
让 collect.py 生成 OUT/stage/manifest.json(files:path、size、sha256、redactions、truncated,按 path 排序,total_redactions)。
sha256 和 size 按脱敏之后实际写入 stage 的字节来计算。redactions 是该文件中脱敏的次数。如果在这个文件里放入生成时间,请先想一想第 6 步会出什么问题。
大日志脱敏之后只留最近的行
对 logs 中每个文件应用 65536 字节的上限,并让 manifest 的 truncated 和 redactions 与截断后的结果相符。
从脱敏结果的末尾截取 65536 字节,然后如果该窗口的第一行不完整就丢弃它。看窗口前一个字节是否为换行符即可判断。次数要去掉被截掉的部分,在剩下的部分里重新统计。
相同输入得到相同字节
让 collect.py 生成 OUT/support-bundle.tar.gz。条目在 support-bundle/ 之下按名称排序,在不同时间、不同 OUT 打包,sha256 也必须相同。
tar 中含有修改时间、所有者、权限和顺序,而 gzip 头部还会再含一次时间。用 Python 的话,请自己构造 TarInfo 固定这些值,并指定 GzipFile 的 mtime。用 GNU tar 打包的话,请参考 reproducible-builds 文档中的选项。
用文件接收客户安全团队的追加规则
让 collect.py 接收 --rules FILE(kind正则表达式),把它应用在默认规则之后,并把次数合并进 manifest。
跳过注释(#)和空行。不要把规则写进代码,而要从文件读取——评分器每次都会给出不同的种类名称和正则表达式,还会检查不带规则运行时,那个值是否还留着。
交付包与供审查的报告
用材料 site 和 rules.tsv 生成 /root/bundle/delivery/support-bundle.tar.gz、/root/bundle/delivery/SHA256SUMS、/root/bundle/delivery/REDACTION-REPORT.tsv。
SHA256SUMS 的格式要能让接收方用 sha256sum -c 来确认。报告中的次数不要照抄 manifest,而要按种类统计包内文件里的标记次数来写。评分器会用现在的 collect.py 从材料重新打包,检查是否与交付件字节相同。