Context
#111/#114 (implemented in #115) explicitly decided decryption stays
server-side: the CI runner only resolves/downloads refs and pushes
still-encrypted .sops.env files; the target host runs sops decrypt
itself, so the age private key never leaves it. That's a deliberate
trust-boundary choice, not an oversight — this issue is about revisiting
it, not a bug report.
What's prompting a second look
Bootstrapping sops on a fresh host turns out to be more annoying than
expected: it isn't packaged in Ubuntu or Debian at all (checked through
Ubuntu 26.04 and Debian sid/trixie/forky) — the only install path is a
pinned binary/.deb from GitHub Releases, same as encrypt-env/action.yml
already does in CI. Every target host needs that bootstrap step repeated,
plus its own age private key provisioned and kept in sync with
keys/<target>.pub.
If decryption moved to the runner instead:
- Target hosts would need neither
sops nor a local age key at all — one
less package to bootstrap, one less secret to provision and rotate per
host. - The age private key would need to live as a GitHub Secret instead of a
file on each server.
The tradeoff (why this isn't a clear win)
Moving the private key into GitHub Secrets changes the blast radius of a
GitHub Secrets compromise: today, decrypting a target's vaults requires
both GitHub Secrets access (to get the ciphertext/refs) and physical
access to that specific host's local key file. Centralizing decryption in
CI collapses that to GitHub Secrets access alone being sufficient to
decrypt everything, for every target, from one place.
With a single target (hawkeye) today, the bootstrap-friction case is real
but small; the security downside is permanent and scales with however many
targets/hosts this ever grows to.
Proposal
Not a decided migration — write up both models properly (what changes in
deploy/deploy.py, deploy-shared.yml's secrets, and each target's
prerequisites) and weigh them once there's more than one real target to
judge the bootstrap-cost side against, rather than deciding on a single
data point.
Related
#111 (decided server-side decryption as part of the push-based redesign
this would reopen), #114 (the per-app vault delivery this decryption model
carries), #106 (hawkeye's prerequisites list, which currently includes
sops + age key setup on the host).
Context
#111/#114 (implemented in #115) explicitly decided decryption stays
server-side: the CI runner only resolves/downloads refs and pushes
still-encrypted
.sops.envfiles; the target host runssops decryptitself, so the age private key never leaves it. That's a deliberate
trust-boundary choice, not an oversight — this issue is about revisiting
it, not a bug report.
What's prompting a second look
Bootstrapping
sopson a fresh host turns out to be more annoying thanexpected: it isn't packaged in Ubuntu or Debian at all (checked through
Ubuntu 26.04 and Debian sid/trixie/forky) — the only install path is a
pinned binary/
.debfrom GitHub Releases, same asencrypt-env/action.ymlalready does in CI. Every target host needs that bootstrap step repeated,
plus its own age private key provisioned and kept in sync with
keys/<target>.pub.If decryption moved to the runner instead:
sopsnor a local age key at all — oneless package to bootstrap, one less secret to provision and rotate per
host.
file on each server.
The tradeoff (why this isn't a clear win)
Moving the private key into GitHub Secrets changes the blast radius of a
GitHub Secrets compromise: today, decrypting a target's vaults requires
both GitHub Secrets access (to get the ciphertext/refs) and physical
access to that specific host's local key file. Centralizing decryption in
CI collapses that to GitHub Secrets access alone being sufficient to
decrypt everything, for every target, from one place.
With a single target (hawkeye) today, the bootstrap-friction case is real
but small; the security downside is permanent and scales with however many
targets/hosts this ever grows to.
Proposal
Not a decided migration — write up both models properly (what changes in
deploy/deploy.py,deploy-shared.yml's secrets, and each target'sprerequisites) and weigh them once there's more than one real target to
judge the bootstrap-cost side against, rather than deciding on a single
data point.
Related
#111 (decided server-side decryption as part of the push-based redesign
this would reopen), #114 (the per-app vault delivery this decryption model
carries), #106 (hawkeye's prerequisites list, which currently includes
sops+ age key setup on the host).