0ba56ed30d
Operator-minted enrollment tokens now carry the repo URL/username/
password as one AEAD blob bound (via additional-data) to the token
hash. ConsumeEnrollmentToken re-encrypts under host_id and writes a
host_credentials row in the same tx as token-burn, so the binding
moves with the credential.
PUT /api/hosts/{id}/repo-credentials lets an operator edit creds
post-enrollment; merges with the existing blob, audits, and pushes
config.update if the agent is connected.
WS handler grows an OnHello hook that the HTTP layer wires to send
the host's decrypted creds as a config.update immediately after the
hello succeeds — synchronously, so a racing command.run lands after
the agent has its repo password.
Schema: 0002_host_credentials.sql adds enc_repo_creds to
enrollment_tokens and a host_credentials table (PK = host_id, FK
ON DELETE CASCADE).
Tests: round-trip token → consume → host_credentials with AAD swap
detection; no-creds path stays compatible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
27 lines
1.1 KiB
SQL
27 lines
1.1 KiB
SQL
-- 0002_host_credentials.sql
|
|
--
|
|
-- Repo credentials carried on the enrollment token, then promoted to
|
|
-- a per-host row on consume. Pulled forward from Phase 2 so the
|
|
-- "Add host" flow is genuinely one-shot — operator supplies repo
|
|
-- creds at token-mint time, agent receives them via config.update on
|
|
-- first WS connect.
|
|
--
|
|
-- See spec.md §7.3 for the threat model and tasks.md P1-32 for the
|
|
-- end-to-end flow.
|
|
|
|
-- Token row optionally carries an AEAD-encrypted JSON blob of
|
|
-- {repo_url, repo_username, repo_password}. AEAD additional-data
|
|
-- binds it to the token_hash so swap attacks between rows fail.
|
|
ALTER TABLE enrollment_tokens
|
|
ADD COLUMN enc_repo_creds TEXT;
|
|
|
|
-- Per-host repo credential, replaces the blob from the token row on
|
|
-- ConsumeEnrollmentToken. AEAD additional-data binds it to host_id.
|
|
-- One row per host; absence means "no creds set yet, agent will
|
|
-- refuse backup jobs until the operator sets them via the UI."
|
|
CREATE TABLE host_credentials (
|
|
host_id TEXT PRIMARY KEY REFERENCES hosts(id) ON DELETE CASCADE,
|
|
enc_repo_creds TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|