Files
ServerKit/docs/pairing.md
T
Juan Denis e5a90a3085 Add short-code agent pairing flow
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.
2026-04-29 03:08:21 -04:00

5.6 KiB

Agent Pairing (Short-Code Flow)

ServerKit supports two ways to attach an agent to the panel:

  1. Pair (recommended) — install the agent once, then claim it from the panel with a 6-character rotating code + your operator passphrase. RustDesk-style.
  2. 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 identifier
  • X-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