Implements a RustDesk-style short-code pairing feature across agent, backend, frontend and docs. Key changes: - Agent CLI: adds `serverkit-agent pair` command (agent/cmd/agent/pair.go) that generates/loads an Ed25519 keypair, enrolls with the panel, shows a rotating pair code, and saves credentials on claim. Adds passphrase marker handling and interactive/headless modes. - Agent internals: new pairing package with client and keypair (agent/internal/pairing/*.go). Keypair files are encrypted using new exported config EncryptBytes/DecryptBytes utilities and MachineID() helper in agent/internal/config/config.go. go.mod/go.sum updated (Go version and x/term, x/sys bumps). - Backend API: new pairing blueprint exposing endpoints for enroll, poll, code refresh/freeze, lookup and claim (backend/app/api/pairing.py). Adds PendingAgent model (backend/app/models/pending_agent.py) to track enrollments and their lifecycle. - Backend service: pairing_service (backend/app/services/pairing_service.py) implements enrollment, rotation, long-polling, claim handling, credential minting and one-shot delivery. Adds a background pairing pruner thread to remove expired pending enrollments (registered in backend/app/__init__.py). - Frontend: add API client support and page changes to integrate pairing flow (frontend/src/services/api/pairing.js, modifications to Servers.jsx and api index). - Docs: adds pairing.md explaining the pairing flow. Why: provide a secure, user-friendly mechanism for operators to claim new agents via short rotating codes and a passphrase, simplifying server onboarding while protecting credentials and preventing brute-force attempts.
5.6 KiB
Agent Pairing (Short-Code Flow)
ServerKit supports two ways to attach an agent to the panel:
- Pair (recommended) — install the agent once, then claim it from the panel with a 6-character rotating code + your operator passphrase. RustDesk-style.
- Token install (legacy) — generate a registration token in the panel, paste it into a curl/PowerShell one-liner. Still supported.
This document covers the pair flow.
Why pairing?
The pre-shared registration token approach has several downsides:
- The token is a long-lived secret embedded in a copy-pasted shell command.
- If anyone observes the install command, they can pre-empt the registration.
- Re-pairing after a wipe means generating a fresh token, repeating the dance.
Pairing replaces this with:
- A per-machine passphrase the operator sets once on the agent host.
- A rotating 6-character pair code the agent displays (CLI banner / tray).
- A public-key fingerprint that's verifiable out-of-band.
The panel claims the agent only after both the code and passphrase match.
Operator flow
1. On the target machine
After installing the agent (e.g. via the install script, package, or MSI), run:
serverkit-agent pair --server https://panel.example.com
You'll be prompted for a passphrase (4+ characters). Pick something memorable — you only need to type it once on the panel side. The agent then:
- Generates an Ed25519 keypair (encrypted at rest with the machine key).
- Submits its public key + bcrypt'd passphrase to the panel.
- Prints a banner with the rotating 6-char pair code and the public-key fingerprint:
┌──────────────────────────────────────────────┐
│ ServerKit Agent — Pairing │
├──────────────────────────────────────────────┤
│ Pair code: ABC-123 │
│ Fingerprint: 1A2B3C4D5E6F7A8B │
├──────────────────────────────────────────────┤
│ 1. Open ServerKit panel → Add Server │
│ 2. Enter the pair code + your passphrase │
│ 3. Verify the fingerprint matches │
└──────────────────────────────────────────────┘
Waiting for claim...
The pair code rotates every ~5 minutes until the agent is claimed.
2. In the panel
Go to Servers → Add Server and select Pair existing agent. Enter:
- The 6-character pair code
- The passphrase you set
- (Optional) A friendly name and group
Verify the fingerprint shown after lookup matches what the agent printed.
Click Pair Agent. The agent receives credentials over its existing
long-poll, saves them, and prints ✓ Pairing successful!.
3. Start the agent
sudo systemctl start serverkit-agent # Linux
# or use Services.msc on Windows
The agent connects to the panel and the server flips from "Pending" to "Online".
Headless / unattended pairing
For Docker, Kubernetes, or unattended provisioning:
SERVERKIT_AGENT_PASSPHRASE='hunter2' \
serverkit-agent pair --server https://panel.example.com --headless
You still need an operator to claim the code in the panel — the headless flag just suppresses interactive prompts.
Local dev / ngrok
If you're testing the panel locally, expose it via ngrok and set:
export SERVERKIT_PUBLIC_URL=https://abcd1234.ngrok.app
Use that URL as the --server flag. For self-signed TLS in dev,
SERVERKIT_INSECURE_TLS=true skips cert verification on the agent side
(don't use this in production).
Security notes
- Pair codes are 6 characters from a 31-symbol alphabet (Crockford-ish base32 minus 0/O/1/I/L). Codes rotate ~every 5 minutes.
- Passphrases are bcrypt'd on the panel before storage.
- Failed claim attempts trigger per-enrollment exponential backoff (60s → 5 min cap) plus per-IP rate limits (5 claims / 10 minutes).
- All claim/lookup events are written to the audit log.
- Enrollments expire 24h after creation; un-claimed records are pruned hourly.
- The agent's keypair is encrypted at rest with a key derived from
/etc/machine-id+ hostname (Linux) or COMPUTERNAME + USERNAME (Windows). - Compare the displayed fingerprint side-by-side before claiming. Mismatch
means the wrong agent — abort and re-run
pair.
API reference
| Endpoint | Purpose | Auth |
|---|---|---|
POST /api/v1/pairing/enroll |
Agent submits pubkey + passphrase | none |
POST /api/v1/pairing/code/refresh |
Agent rotates its pair code | enrollment headers |
POST /api/v1/pairing/code/freeze |
Agent freezes/unfreezes rotation | enrollment headers |
GET /api/v1/pairing/poll |
Agent long-polls for claim (≤25s) | enrollment headers |
POST /api/v1/pairing/lookup |
Panel resolves code → fingerprint | JWT |
POST /api/v1/pairing/claim |
Panel claims an enrolled agent | JWT |
Headers used by the agent:
X-Enrollment-Id: opaque enrollment identifierX-Enrollment-Secret: long-lived secret returned at enroll time
Comparison with token install
| Pair | Token install | |
|---|---|---|
| Out-of-band step | passphrase typed into panel | token pasted into shell |
| Compromise window | 5 min (rotating code) | 24h (token TTL) |
| Re-pair after wipe | re-run pair |
regenerate token |
| Headless friendly | yes (--headless + env) |
yes (curl one-liner) |
| Recommended? | ✅ | only for legacy/scripted flows |