feat(server): self-contained enrollment bundle + one-time fetch endpoint; drop legacy .vagent minting #89

Merged
Claude-Run merged 1 commit from feat/VS-69-enroll-bundle-fetch into main 2026-06-28 22:17:23 +02:00
Member

Stamp the agent mTLS domain into the enrollment bundle and add a single-use bundle-fetch endpoint so a headless host can fetch-and-install in one line (VS-69).

New config VERVAIN_AGENT_PUBLIC_DOMAIN (host:port) names the externally reachable agent mTLS endpoint, distinct from VERVAIN_BASE_URL (the browser/Traefik 443 host) because the mTLS port is dialed directly. Precedence is settings.agent_public_domain -> env -> derived from the public host plus the agent bind port; read_agent_public_domain returns None only when nothing is set and no host can be derived, and the mint refuses rather than ship a domain-less bundle.

POST /api/v1/enroll-tokens now returns the full bootstrap.json (with domain) inline plus a one-time bundle_token, and stores the bundle keyed by the SHA-256 hash of that token. GET /api/v1/enroll-bundle?token= returns the stored bundle once and consumes it atomically: valid -> 200, unknown/empty token -> 401, already-used or expired -> 410. The endpoint is unauthenticated (the target host has no session) but per-IP rate-limited (reusing the token-bucket limiter, now IpRateLimiter) and single-use. Only the token hash is stored, so a DB leak yields no usable token and there is no constant-time-compare obligation. New DB schema v3 adds the enroll_bundles table with insert_enroll_bundle / consume_enroll_bundle.

Delete the legacy .vagent / GroupServer minting: removed groupsettings.rs, its module declaration, and the /groupsettings + /groupsettings.ashx routes (root and per-domain). A guard test asserts those routes now 404 and mint no .vagent body, so the removal is enforced mechanically.

#VS-69

Stamp the agent mTLS `domain` into the enrollment bundle and add a single-use bundle-fetch endpoint so a headless host can fetch-and-install in one line (VS-69). New config `VERVAIN_AGENT_PUBLIC_DOMAIN` (`host:port`) names the externally reachable agent mTLS endpoint, distinct from `VERVAIN_BASE_URL` (the browser/Traefik 443 host) because the mTLS port is dialed directly. Precedence is `settings.agent_public_domain` -> env -> derived from the public host plus the agent bind port; `read_agent_public_domain` returns `None` only when nothing is set and no host can be derived, and the mint refuses rather than ship a domain-less bundle. `POST /api/v1/enroll-tokens` now returns the full `bootstrap.json` (with `domain`) inline plus a one-time `bundle_token`, and stores the bundle keyed by the SHA-256 hash of that token. `GET /api/v1/enroll-bundle?token=` returns the stored bundle once and consumes it atomically: valid -> 200, unknown/empty token -> 401, already-used or expired -> 410. The endpoint is unauthenticated (the target host has no session) but per-IP rate-limited (reusing the token-bucket limiter, now `IpRateLimiter`) and single-use. Only the token hash is stored, so a DB leak yields no usable token and there is no constant-time-compare obligation. New DB schema v3 adds the `enroll_bundles` table with `insert_enroll_bundle` / `consume_enroll_bundle`. Delete the legacy `.vagent` / `GroupServer` minting: removed `groupsettings.rs`, its module declaration, and the `/groupsettings` + `/groupsettings.ashx` routes (root and per-domain). A guard test asserts those routes now 404 and mint no `.vagent` body, so the removal is enforced mechanically. #VS-69
feat(server): self-contained enrollment bundle + one-time fetch endpoint; drop legacy .vagent minting
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 55s
Create release / Create release from merged PR (pull_request) Has been skipped
9a0a4d5fd5
Stamp the agent mTLS `domain` into the enrollment bundle and add a single-use bundle-fetch endpoint so a headless host can fetch-and-install in one line (VS-69).

New config `VERVAIN_AGENT_PUBLIC_DOMAIN` (`host:port`) names the externally reachable agent mTLS endpoint, distinct from `VERVAIN_BASE_URL` (the browser/Traefik 443 host) because the mTLS port is dialed directly. Precedence is `settings.agent_public_domain` -> env -> derived from the public host plus the agent bind port; `read_agent_public_domain` returns `None` only when nothing is set and no host can be derived, and the mint refuses rather than ship a domain-less bundle.

`POST /api/v1/enroll-tokens` now returns the full `bootstrap.json` (with `domain`) inline plus a one-time `bundle_token`, and stores the bundle keyed by the SHA-256 hash of that token. `GET /api/v1/enroll-bundle?token=` returns the stored bundle once and consumes it atomically: valid -> 200, unknown/empty token -> 401, already-used or expired -> 410. The endpoint is unauthenticated (the target host has no session) but per-IP rate-limited (reusing the token-bucket limiter, now `IpRateLimiter`) and single-use. Only the token hash is stored, so a DB leak yields no usable token and there is no constant-time-compare obligation. New DB schema v3 adds the `enroll_bundles` table with `insert_enroll_bundle` / `consume_enroll_bundle`.

Delete the legacy `.vagent` / `GroupServer` minting: removed `groupsettings.rs`, its module declaration, and the `/groupsettings` + `/groupsettings.ashx` routes (root and per-domain). A guard test asserts those routes now 404 and mint no `.vagent` body, so the removal is enforced mechanically.

#VS-69
David deleted branch feat/VS-69-enroll-bundle-fetch 2026-06-28 22:20:05 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-server!89
No description provided.