管理密钥
fork 的问题是这样的:它们是加密镜像的逐字节拷贝,包含所有凭据。Stripe 生产密钥、数据库密码、仓库里的 API 令牌?Fork 会继承它们。你的沙盒最终会向真实客户收费。
正确的地方是 rdc repo secret。两种交付模式,设计上只写不读,fork 启动时什么都没有。本教程中,我们设置两种模式,部署一个使用它们的应用,验证值真正到达容器,然后亲眼看着 fork 因密钥拒绝随行而启动失败。
观看教程
陷阱:仓库里的 .env
大多数团队把 .env 放在仓库里。这是显而易见的做法。
然后他们 fork 了。
fork 是父仓库镜像的逐字节拷贝。.env 里有什么,fork 的 .env 里就有什么。fork 的容器启动了,读取了相同的 Stripe 密钥,用生产凭据调用了相同的 Stripe API。从 Stripe 的角度看,那次调用就是你发出的。
那会是糟糕的一天。问问我怎么知道的。
第 1 步:设置 env 模式密钥
rdc repo secret set --name my-app --key DB_HOST --value postgres.internal --mode env 首先,以 env 模式设置一个密钥。该值将作为环境变量注入容器内。首次写入无需任何验证,覆盖已有密钥才需要证明。
--mode env 使值作为环境变量落入容器。首次写入无需额外手续;覆盖已有密钥时才需要提供当前值作为证明。
第 2 步:设置 file 模式密钥
rdc repo secret set --name my-app --key STRIPE_KEY --value sk_test_xxx --mode file 现在设置一个 file 模式密钥。file 模式不会通过容器环境变量暴露值,而是使用 Docker 标准密钥机制将值写入 /run/secrets 下的文件。凡是敏感内容,请优先使用 file 模式。
file 模式永远不会将值放入容器的环境变量。它会使用 Docker 的标准机制将值写入 /run/secrets/stripe_key。对任何敏感内容都应优先使用此模式。
第 3 步:列出现有密钥
rdc repo secret list --name my-app 来列出我们已有的密钥。只显示名称和模式,无论谁在查询,列表都不会显示值。
你看到的是名称和模式。没有值。 无论谁来查询,列表永远不显示值。
接入 compose
打开 docker-compose.yml,引用两种模式:
services:
api:
image: myapp:latest
environment:
DATABASE_HOST: ${REDIACC_SECRET_DB_HOST}
secrets:
- stripe_key
secrets:
stripe_key:
file: /var/run/rediacc/secrets/${REDIACC_NETWORK_ID}/STRIPE_KEY
${REDIACC_SECRET_DB_HOST} 是 env 模式:renet 的 compose 包装器在部署时从密钥存储中展开它。
secrets: 块是 file 模式,使用 Docker 的标准机制。宿主机路径使用 ${REDIACC_NETWORK_ID},使同一个 compose 文件适用于父仓库和 fork。每个 fork 有自己的网络 ID。
永远无法读回密钥值
这是第一次用时会让人意外的部分,我自己也不例外。
第 4 步:Get 返回摘要
rdc repo secret get --name my-app --key STRIPE_KEY secret get 命令返回摘要而非值本身,且没有参数可以恢复明文。这遵循 GitHub Actions 的模型:密钥在设计上是只写的。
你得到的是摘要。不是值。 没有任何标志可以让它返回值。没有任何命令能给你返回明文。
这就是 GitHub Actions 模型:只写设计。你可以通过传入 --current <value> 并看到前置条件通过来证明你知道密钥的内容。但你无法要求 Rediacc 告诉你它是什么。
第 5 步:忘记时进行轮换
忘记了值?别去偷看。轮换它。
rdc repo secret set --name my-app --key STRIPE_KEY --value sk_test_new --mode file --rotate-secret 如果您忘记了密钥的值,请轮换它,而不是尝试恢复。rotate-secret 参数会跳过前置条件检查,审计日志会将此次更改记录为蓄意轮换。
--rotate-secret 跳过前置条件检查。审计日志将此次变更标记为轮换操作:明确、有意为之。
如果你确实记得旧值,可以用 --current <old-value> 来证明。这是更安全的路径。它帮我发现过好几次”我在错误的终端里,操作了错误的机器”的失误。
部署并验证交付
从未到达应用的密钥只是一个精致的数据库。部署并检查两种交付路径。
第 6 步:携带两个密钥部署
rdc repo up --name my-app --machine <machine-name> 部署 repo。compose 文件同时使用两个密钥:env 值通过插值注入,file 值通过 Docker secrets 挂载注入。
第 7 步:env 密钥到达
rdc term connect --machine <machine-name> --repository my-app --command 'docker exec app printenv DB_HOST' 在容器内打印变量:postgres.internal。env 模式密钥在部署时已送达应用。
容器打印出 postgres.internal。应用确实收到了值,在部署时被展开到环境变量中。
第 8 步:file 密钥到达
rdc term connect --machine <machine-name> --repository my-app --command 'docker exec app cat /run/secrets/stripe_key' 在容器内读取 /run/secrets/stripe_key:轮换后的值已挂载于此。应用获取明文,只有 CLI 拒绝显示它。
那就是轮换后的值,从容器内的 /run/secrets/stripe_key 读取。只写规则针对的是人类和 CLI;你的应用在 Docker 承诺的位置拿到的是真实明文。
fork 的结局
还记得那个陷阱吗?Fork 仓库然后看看。
第 9 步:Fork 仓库
rdc repo fork --parent my-app --tag test --machine <machine-name> Fork 该 repo。fork 是父级加密镜像的逐字节副本。
第 10 步:Fork 列表为空
rdc repo secret list --name my-app:test 列出 fork 的密钥返回空集:没有 Stripe 密钥,没有数据库密码,没有 API token。fork 无法冒充父级,这正是克隆生产环境安全的原因。
空的。
fork 没有 Stripe 密钥,没有数据库密码,没有 API 令牌。fork 中的容器无法展开 ${REDIACC_SECRET_STRIPE_KEY}。/var/run/rediacc/secrets/<fork-id>/STRIPE_KEY 这个文件并不存在。
fork 无法伪装成你。
第 11 步:Fork 甚至无法启动
rdc repo up --name my-app:test --machine <machine-name> 使用父级 compose 启动 fork 失败:密钥文件在 fork 的网络 ID 下不存在,Docker 拒绝绑定挂载。生产凭据永远不会随 fork 转移。
部署故意失败:bind source path does not exist: /var/run/rediacc/secrets/<fork-id>/STRIPE_KEY。密钥文件存在于父仓库的网络 ID 下,而非 fork 的,因此 Docker 拒绝挂载。这个失败就是演示本身:生产凭据永远不会跟随 fork,哪怕是意外。
如果你想在 fork 中使用密钥进行测试,用沙盒值在 fork 上单独设置,例如 rdc repo secret set --name my-app:test --key STRIPE_KEY --value sk_sandbox_yyy --mode file。这样 fork 与 Stripe 沙盒通信,可以正常启动。生产凭据从未离开生产环境。
总结
rdc repo secret将凭据存放在仓库镜像之外。- 两种模式都真正到达容器:env 展开和
/run/secrets。 get返回摘要,而不是值。忘记了就轮换,不要去偷看。- Fork 列表为空,甚至无法启动父仓库的 compose。
密钥,fork 无法追随。
下一篇:备份与恢复。