feat(web): gate agent-binary download with scoped install tokens (VS-57) #77
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/VS-57-authorize-agent-binary-download"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Stacked on #74 (VS-56). Base is
feat/VS-56-public-agent-binary-download; review/merge that first. The diff here is only the VS-57 authz layer.What
Gates the public agent-binary download endpoint (
/api/v1/agent/binary/:id, VS-56) so it is not an open redistribution point on the API host.Why this model
The endpoint's real consumer is the device-side install snippet's unattended
curl(curl -fL -o vervain-agent <api>/api/v1/agent/binary/6), which runs minutes/hours after the operator generates it and carries novervain_session. A browser-session gate or the single-use 60s control-token (VS-32) does not fit. Chosen model (confirmed with the maintainer): a scoped, expiring install token.How
InstallTokenStoremirrors the VS-32 control-token store but is multi-use within a 24 h lifetime - one generated snippet enrolls a batch of devices within a day, then the URL stops working. In-memory only (a restart clears outstanding tokens; regenerate the snippet), carrying the minting user id for audit.GET /agentbinaries/install-tokenmints one, gated by the existing session, returning{"token", "ttl_secs"}. The apps enrollment flow (get_group_install) will call it with the forwarded session and bake the token into the download URL as?token=(that wiring is VAPP-35).401with no body and no Forgejo contact.Acceptance criteria
401, no binary bytes, no upstream detail (testsdownload_401_without_install_token,download_401_with_unknown_install_token).404only after the token passes (download_404_when_authorized_but_no_fetcher).Test
cargo clippy -p vervain-web --all-targets -- -D warningsclean; full pre-commit (fmt + clippy + build + test) green, 448 tests pass incl. 10 new.The public agent-binary download endpoint (VS-56, /api/v1/agent/binary/:id) is on the API host, so it must not be an open redistribution point. Its real consumer is the device-side install snippet's unattended curl, which carries no vervain_session, so a browser-session or single-use control-token gate does not fit. Add a scoped, expiring install token instead. InstallTokenStore mirrors the VS-32 control-token store but is multi-use within a 24 h lifetime: one generated snippet can enroll a batch of devices within a day, after which the URL stops working. Tokens are in-memory only (a restart clears them; the operator regenerates the snippet) and carry the minting user id for audit. GET /agentbinaries/install-token mints one, gated by the existing session, returning {token, ttl_secs}; the apps enrollment flow (get_group_install) calls it with the forwarded session and bakes the token into the download URL as ?token=. The download handler validates the token FIRST, before any upstream read, so an absent/unknown/expired token is rejected with 401, no body, and no Forgejo contact. The apps repoint that mints + appends the token is VAPP-35. README documents the who/credential/lifetime model; the VS-56 deferral notes in wire_paths are updated now that the gate exists. #VS-57 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Closing as superseded by VS-60 (#79), already merged to main.
VS-60 built exactly what this PR (VS-57) targeted: it mints the scoped install token and gates
/api/v1/agent/binary/:idbefore any upstream read. Its own commit message notes it adds "the client-facing authorization VS-57 left unbuilt." VS-60 landed a deliberately different (maintainer-chosen) model: a 5-minute token that the handler accepts as EITHER a validvervain_sessionOR a?token=, versus this PR's token-only 24h model.Everything in this PR is now either already in main or intentionally replaced:
Merging would re-introduce the older token-only model over VS-60 and require resolving heavy conflicts in install_token.rs / lib.rs / wire_paths.rs. No salvageable delta, so closing rather than rebasing.
Pull request closed