仅让已验证的交付包进入指南

ShellBeginner
立即练习

介绍

只有让已验证的字节成为运行中的发布,验证交付物才有意义。Waymark 需要一个可复用的接收脚本,在修改徒步节指南之前拒绝未获批准的交付包。

你将替换一段简短的 shell 脚本,实际验证其拒绝判断,并发布主办方请求的更新。这个独立环境从原始指南和它自己的交付包开始。前一个引导实验已教授签名、独立信任的公钥、请求的元数据和归档摘要。现在你将组合这些知识,无需编写 Python 程序。

理解现有指南和接收脚本契约

本步骤确定更新必须改变什么、保留什么。打开 Event Guide,再打开 Old Mill Walk。原始 event-guide 版本为 2.0,路线集合时间是 11:00。使用 All walks 返回并下载路线表。更新后,详情页和下载都必须正常工作。

指南首页在标题上方标明版本,并在每条路线卡片上显示集合时间:

更新前正常运行的 Waymark 指南

切换到 Workspace。cd 改变目录;以下绝对路径适用于任何当前位置:

cd /home/labex/project/waymark

cat 显示文件。阅读独立提供的更新请求:

cat /opt/labex-waymark/trust/request.json

请求的产品是 event-guide,版本为 2.1。获批更新将 Old Mill Walk 的时间改为 14:00。另一条路线仍为 09:30。阅读初始脚本:

cat receive.sh

初始脚本从交付包自身取出摘要并传给发布程序。另一位发布者同样可以提供相互匹配的清单和归档。该脚本还未验证签名或主办方请求的产品/版本。你将在发布前替换它。

要求的调用接口为:

bash receive.sh CANDIDATE_DIRECTORY DESTINATION_DIRECTORY

第一个参数是供应商交付包。第二个参数是指南发布目录;线上项目中为 published。两个路径都可以是绝对或相对路径,可以重命名,也可以含空格。不要硬编码交付标签或目标路径。更新成功返回零;拒绝返回非零,并保持所有已有发布文件不变。合法更新保留历史发布和无关上传文件。

你可以编辑 receive.sh 并创建临时文件。保持原始交付包、独立公钥/请求、准备好的发布程序和其他运行文件不变。接收脚本单独复制到另一个目录后,在同样的工具和独立信任文件可用时也必须正常工作。评分会在一次性目录中验证此契约,同时检查真实指南。

如果路径输入错误,运行上面的绝对路径 cd,用 ls candidates 查看标签,然后重试命令。检查失败时应查看相关证据,无需重置环境。

设计能在发布前停止的接收脚本

本步骤先理解短脚本,再编写它。留在 Workspace。

接收脚本会将三个关联文件全部复制到私有临时目录,验证复制后的清单,将产品/版本与独立请求比较,再把复制后的归档和已签名摘要传给预先准备的发布程序。它不会验证一个候选包后,再打开另一个候选路径进行交付。

发布程序 /opt/labex-waymark/publish.py 是预先准备的归档处理代码。它将所提供归档读入一个字节缓冲区,用提供的摘要检查缓冲区,安全地仅解压 site.json 和 routes.csv,然后发布同一份字节。它保留早期发布和 uploads/。它不会决定信任谁的签名,也不会决定主办方请求哪个产品/版本;这些判断属于你的接收脚本。

你将用到以下 shell 特性:

  • #!/bin/bash 指定脚本的 shell。运行 bash receive.sh 也会明确选择 Bash。
  • set -euo pipefail 让未处理的命令失败、未设置变量或管道失败停止脚本。签名失败绝不能继续进入发布操作。
  • $1 和 $2 是两个参数。candidate=$1 保存一个值;"$candidate" 将它展开为一个参数,保留空格。$# 统计参数数目,[ "$#" -ne 2 ] 检查该数目是否不等于二。if、then 和 fi 标定条件块。
  • $(command) 保存命令输出。mktemp -d 创建唯一临时目录。trap '...' EXIT 在脚本结束时执行清理。这里 rm -rf -- "$stage" 只删除生成的临时目录;绝不要替换为项目目录。-- 结束选项解析,让路径被视为数据。
  • cp -- SOURCE SOURCE SOURCE DIRECTORY/ 复制三个文件。结尾斜杠标明目标目录。
  • jq -r .product FILE 读取 JSON 字符串但去掉 JSON 引号。jq -e 在结果为假时返回失败状态。--arg product "$product" 向过滤器传入名为 $product 的字符串变量。and 要求所有条件成立。单引号为 jq 保留过滤器文本。> /dev/null 丢弃打印的布尔值,但保留退出状态。
  • echo ... >&2 将错误写入错误流;exit 2 以非零状态结束。

签名命令使用与前一个实验相同的独立固定公钥。其 --bundle 指向复制后的签名包,最后一个参数是复制后的清单。--insecure-ignore-tlog 是这些离线合成材料明确的限制:跳过公共日志验证,但仍检查固定公钥下的签名。此接收脚本不是公共身份或透明日志验证器。

最后一条 Python 命令调用预先准备的发布程序;你无需编写 Python。它接收复制后的归档、已验证清单中的摘要和所请求的目标目录。如果摘要与归档字节不符,它会在触及现有指南前拒绝更新。

保存可复用的接收脚本

本步骤使用刚刚学习的机制编写接收脚本。从 Workspace 的 /home/labex/project/waymark 目录开始。

以下命令替换 receive.sh。cat > receive.sh 将提供的文本重定向到文件。<<'RECEIVER' 开始 here-document:直到仅包含 RECEIVER 的一行之前的所有内容都成为文件内容。为标记加引号可以保留 $1、$stage 和 jq 变量,留待脚本之后执行时使用。粘贴完整代码块,包括最后的标记:

cat > receive.sh <<'RECEIVER'
#!/bin/bash
set -euo pipefail
if [ "$#" -ne 2 ]; then
  echo "Usage: bash receive.sh CANDIDATE_DIRECTORY DESTINATION_DIRECTORY" >&2
  exit 2
fi
candidate=$1
destination=$2
stage=$(mktemp -d)
trap 'rm -rf -- "$stage"' EXIT
cp -- "$candidate/manifest.json" "$candidate/manifest.sigstore.json" "$candidate/guide.tar.gz" "$stage/"
/opt/labex-waymark/bin/cosign verify-blob --key /opt/labex-waymark/trust/publisher.pub --bundle "$stage/manifest.sigstore.json" --insecure-ignore-tlog "$stage/manifest.json"
product=$(jq -r .product /opt/labex-waymark/trust/request.json)
version=$(jq -r .version /opt/labex-waymark/trust/request.json)
jq -e --arg product "$product" --arg version "$version" '.product == $product and .version == $version and .archive == "guide.tar.gz"' "$stage/manifest.json" > /dev/null
digest=$(jq -r .sha256 "$stage/manifest.json")
python3 /opt/labex-waymark/publish.py "$stage/guide.tar.gz" "$digest" "$destination"
RECEIVER

重新读取:

cat receive.sh

确认公钥和请求来自 /opt/labex-waymark/trust/,而清单、签名包和归档全部来自 "$stage"。注意复制后不会再次读取原始候选目录。因此,在复制后更改或重命名供应商交付目录,都无法调换传给发布程序的字节。

如果终端因缺少结束标记而显示续行提示,在单独一行输入 RECEIVER 并按 Enter。如果文件粘贴错误,重复完整 here-document;它只替换 receive.sh。不要运行不完整的接收脚本。

预先准备的策略检查会以普通账户身份,在一次性发布目录中原样运行你的脚本。它检查实际发布文件、拒绝案例和保留行为,不要求特定的命令拼写:

sudo python3 /opt/labex-waymark/verify.py policy

此检查需要 sudo 来运行隔离的普通账户,你的接收脚本不需要它。检查不改动线上指南。最后一行预期确认接收所请求的已签名指南,并拒绝四种未获批准的情况。如果失败,查看报告中的判断,在继续之前修正 receive.sh。

在更新线上指南前观察拒绝行为

本步骤确认接收脚本会在三种不同失败情形下停止,原始指南仍可使用。从 Workspace 的 /home/labex/project/waymark 目录开始。

先尝试另一位发布者签署的交付包。预期出现签名验证错误和非零状态:

bash receive.sh candidates/parcel-102 published

$? 保存紧前一条命令的退出状态。运行其他命令前先读取它:

echo $?

现在尝试真实但较早的版本。预期签名验证通过,随后因为请求的产品/版本比较为假而返回非零状态:

bash receive.sh candidates/parcel-101 published
echo $?

jq 比较刻意不输出文本;非零状态表示拒绝。有效签名不能覆盖独立请求。

最后尝试已改动的归档。预期签名通过,但发布程序报告摘要不符,并保留现有指南:

bash receive.sh candidates/parcel-103 published
echo $?

切换到 Event Guide 并刷新。指南必须仍为 2.0;Old Mill Walk 仍须在 11:00 集合。使用 All walks 返回,下载路线表,并打开 Visitor notice。拒绝所有更新的接收脚本也能保留旧指南,因此还必须验证合法更新成功。

文件缺失或连接错误不同于这些预期的策略拒绝。如果交付包路径拼写错误,返回工作区的绝对路径,列出 candidates,然后重试。如果网站不可用,查看报告的错误;不要将其称为成功拒绝。

发布并使用获批的新指南

本步骤交付已验证的更新,并使用其实际页面和下载。从 Workspace 的 /home/labex/project/waymark 目录开始。

接收获批交付包:

bash receive.sh candidates/parcel-104 published

预期看到 Verified OK 以及:

Published event-guide 2.1. Previous releases and uploads kept.

立即检查状态:

echo $?

预期为 0。切换到 Event Guide 并刷新。首页现在显示 event-guide 版本 2.1。打开 Old Mill Walk,确认时间为 14:00,然后使用 All walks 返回,再打开 Riverside Welcome,确认其 09:30 集合时间被保留。使用 All walks 返回并下载新的路线表。两行数据必须与详情页一致。打开 Visitor notice,确认原始建议仍在。

获批首页显示版本 2.1;注意 Old Mill Walk 的 14:00 集合时间:

已发布的获批 Waymark 指南

切换到 Workspace。线上自检读取运行中的详情页、下载的路线表字节、当前指南文件、保留的历史发布和须知:

python3 /opt/labex-waymark/verify.py live

预期输出:

The running guide and download match the approved release. Previous releases and visitor notice are preserved.

ls 列出保留的发布目录:

ls published/releases

其中应有之前的 event-guide-2.0-... 发布和新的 event-guide-2.1-... 发布。结尾字符来自实际归档摘要,无需记忆。

如果线上检查失败,依据消息区分接收判断失败、网站不可用或原始文件被修改。按需修正接收脚本,再次接收原始获批交付包。重复同一个有效更新是安全的:发布程序复用相同发布并保留原有文件。重复线上自检以确认结果。

总结

你将原先信任交付包自身的接收脚本,替换为检查独立发布者公钥和主办方请求的脚本,再把已签名的归档摘要与实际发布的字节绑定起来。错误签名者、真实旧版本和已改动归档都被拒绝,原有服务仍可使用。

获批的更新现在已成为真实指南,详情页正常,路线下载与内容一致。挑战会把同样的工具用于无障碍指南,并加入一个由真实发布者签署但产品不符的交付包。