Device Registration, mTLS Provisioning, and Hardware-Key Tunnel Authorization
This page describes how a user registers a device, how the platform issues that device a short-lived mTLS client certificate from a CLI-generated CSR (or accepts a tenant BYOK CA), and how the device-paired reverse tunnel is gated by a hardware key (FIDO2).
The system of record is the IAM hub, alphaswarm_auth. The reverse-tunnel
relay lives in alphaswarm_controller. The connector + CSR generation live
in alphaswarm_cli. The operator UI lives in alphaswarm_ui.
End-to-end flow
Layered security model
Three independent factors gate a tunnel:
- Device certificate (machine identity) — a short-lived (~24h) X.509
client cert whose SAN URI is
spiffe://<trust_domain>/device/<device_id>. Rides the tunnel transport on/tunnel/agent. - Device credential (DC) — the existing HS256 token
(
token_use=device_credential,aud=alphaswarm-tunnel) that authenticates the connector socket. Minted by redeeming a pairing code. - Hardware key (FIDO2) — gates enrollment (pairing + CSR signing, when
the step-up flags are on) and use (
/tunnel/proxyrequires a freshhwkstep-up on the user's token T, RFC 9470).
The mTLS certificate's private key is generated on the device and never
leaves it — only the PEM CSR is transmitted. The CA private key is held
envelope-encrypted (Vault Transit when VAULT_ADDR is set, else a local
AES-256-GCM fallback).
Operator surfaces
- CLI:
alphaswarm-cli connect redeem <code>->auth yubikey login->connect provision-cert->connect up. Certs auto-renew before expiry onconnect up(best-effort; a missing hardware-key step-up prints a hint). - UI: Settings -> Devices & Keys lists devices + their certificates, enrolls hardware keys (reusing the existing passkey ceremony), and revokes certs (revocation is hardware-key step-up gated).
- Kill-switch: fans out to
POST /tunnel/halt(closes live tunnels) andPOST /auth/devices/halt(revokes every device credential + certificate) alongside the existing halt endpoints.
REST surface (alphaswarm_auth)
POST/GET/DELETE /auth/devices,POST /auth/devices/pair,POST /auth/devices/pair/redeem,POST /auth/devices/halt.POST /auth/devices/{id}/certificate(CSR -> signed cert),GET /auth/devices/{id}/certificate,DELETE /auth/devices/{id}/certificate/{serial}.GET /auth/ca/trust-bundle,GET /auth/ca/revocations(public PKI artifacts),POST/GET /auth/ca/trust-anchors(BYOK).- WebAuthn:
GET/POST /auth/yubikey/{register,authenticate}/{options,verify}(fido2 2.x; the verify body carries an opaquestate).
Configuration
alphaswarm_auth (ALPHASWARM_AUTH_*):
POSTGRES_DSN— async DSN (empty -> local sqlite; RLS is Postgres-only).WEBAUTHN_RP_ID/WEBAUTHN_RP_NAME/WEBAUTHN_ORIGINS— MUST match the served domain (e.g.app.alpha-swarm.ai). The legacy defaultalphaswarm.localonly works for a local CLI.WEBAUTHN_REQUIRE_USER_VERIFICATION/WEBAUTHN_REQUIRE_RESIDENT_KEY(default true),WEBAUTHN_REQUIRE_HARDWARE_KEY(reject platform passkeys),WEBAUTHN_ALLOWED_AAGUIDS(optional allow-list).PAIRING_REQUIRE_STEP_UP(default false),CERTIFICATE_REQUIRE_STEP_UP(default true),STEP_UP_MAX_AGE_SECONDS(default 300).CA_ENABLED,CA_TRUST_DOMAIN(defaultalpha-swarm.ai),CA_COMMON_NAME,CA_ROOT_TTL_DAYS,CA_CERT_TTL_SECONDS(default 24h),CA_KEY_ALGORITHM(ec|rsa),BYOK_ENABLED.VAULT_ADDR+LOCAL_ENVELOPE_KEY— CA-key-at-rest envelope.
alphaswarm_controller (ALPHASWARM_CP_*):
TUNNEL_REQUIRE_MTLS— require a valid device cert on/tunnel/agent.TUNNEL_CLIENT_CERT_HEADER— verified-client-cert header set by an mTLS-terminating ingress (nginx$ssl_client_escaped_cert/ Envoy XFCC). Absent -> the relay falls back to an app-level proof-of-possession challenge over the WebSocket.TUNNEL_REQUIRE_HARDWARE_KEY+TUNNEL_STEP_UP_MAX_AGE_SECONDS— gate/tunnel/proxyon a freshhwkstep-up.TUNNEL_CA_BUNDLE_TTL_SECONDS— CA-bundle/CRL cache TTL.
alphaswarm_cli: ALPHASWARM_CLI_WEBAUTHN_ORIGIN (defaults to
https://alphaswarm.local; set to the served origin in hosted deployments).
Persistence
alphaswarm_auth owns the schema (Alembic 0001_devices_webauthn_ca):
devices, pairing_codes, device_credentials, webauthn_credentials,
ca_keys, device_certificates, auth_audit_events. The browseable tables
(devices, webauthn_credentials, device_certificates) carry
FORCE ROW LEVEL SECURITY keyed on the per-transaction
app.current_workspace_id GUC; the secret-keyed tables (pairing_codes,
device_credentials) are looked up by their high-entropy secret. The
application MUST connect as a non-superuser role without BYPASSRLS.
Session revocation integration
POST /auth/devices/halt is the integration point for the monolith's
session-revocation cleanup (hard rule 53): on session revoke / user delete,
call it (with the user's token) to revoke every device credential + cert, and
call the control-plane POST /tunnel/halt to drop live sockets.
Caveats
- The reverse-tunnel registry is single-replica (process-local). Horizontal
scaling needs sticky routing by
device_idor shared pub/sub. - A deleted device cascade-removes its certificates; explicit
DELETE .../certificate/{serial}keeps the row and lists it on the CRL until expiry.