Zum Inhalt springen
Registrierungsprotokoll

Registrierungsprotokoll

Die HTTP-API zwischen dem Helper eines Geräts und claimward-vpn-server, ihre Fehler und der gRPC-RouteService.

Der Wire-Vertrag zwischen den Clients und dem Server. Er ist einmal definiert, in claimward-vpn-client/pkg/protocol (HTTP) und proto/routes/v1/routes.proto (gRPC), und wird von beiden Seiten importiert.

Jede authentifizierte Anfrage sendet das Bearer-Token der Person als Authorization: Bearer <token>, niemals im Body: ein GitHub-OAuth-Access-Token (AUTH_PROVIDER=github), ein OIDC-ID-Token (oidc) oder ein go-authn-Access-Token (go-authn). Anfrage-Bodys sind JSON, höchstens 64 KiB, und unbekannte Felder werden abgelehnt.

POST /api/v1/enroll

Anfrage:

{
  "public_key": "<wireguard base64 public key>",
  "device": { "name": "alice-mbp", "os": "darwin", "platform": "app-osx" },
  "tenant": "hpc"
}

tenant ist optional: Ohne ihn verwendet der Server den einzigen Mandanten der Person. Die Helper senden als os darwin, linux oder windows und als platform app-osx, app-linux oder app-windows.

Antwort 200:

{
  "assigned_ip": "10.80.0.5/32",
  "server_public_key": "<base64>",
  "endpoint": "vpn.example.com:51820",
  "allowed_ips": ["10.80.0.0/24"],
  "dns": ["10.80.0.1"],
  "grpc_endpoint": "vpn.example.com:8444",
  "persistent_keepalive": 25,
  "lease_expires_at": "2026-10-06T10:00:00Z"
}

allowed_ips und dns sind die des Mandanten. dns entfällt, wenn der Mandant keine hat, grpc_endpoint, wenn der Server keinen RouteService ankündigt (GRPC_ENDPOINT). Das Protokoll kennt außerdem ein optionales mtu, das der Server (v0.2.0) nie setzt.

GET /api/v1/tenants

Antwort 200: die Mandanten, mit denen sich der Aufrufer verbinden darf.

[ { "id": "default", "name": "Default" } ]

POST /api/v1/heartbeat

{ "public_key": "<base64>" }

Antwort 200: { "lease_expires_at": "..." }. 404 not_enrolled, wenn der Schlüssel nicht vom Aufrufer registriert ist, 403 not_a_member, sobald die Person den Mandanten der Sitzung verlassen hat, 403 key_not_registered (go-authn), sobald der Anbieter den Schlüssel nicht mehr führt.

POST /api/v1/deregister

{ "public_key": "<base64>" }

Antwort 204, auch für einen Schlüssel, der nicht registriert ist. 403 not_owner, wenn der Schlüssel einer anderen Person gehört.

GET /healthz

Antwort 200: { "status": "ok" }, ohne Authentifizierung.

Fehler

Antworten außerhalb von 2xx tragen:

{ "error": "invalid_token", "message": "…" }
CodeStatusBedeutung
missing_token / invalid_token401kein Bearer-Token, oder die Prüfung durch den Anbieter ist fehlgeschlagen (einschließlich einer Allowlist für Organisationen oder Domains)
bad_public_key / bad_request400fehlerhafte Eingabe
key_not_registered403go-authn: Der Schlüssel ist beim Anbieter nicht von dieser Person registriert
not_a_member403der angeforderte Mandant gehört nicht (oder nicht mehr) zu denen des Aufrufers
not_owner403deregister: Der Schlüssel gehört einer anderen Person
not_enrolled404heartbeat: keine Registrierung dieses Schlüssels durch den Aufrufer
key_taken409enroll: Der Schlüssel ist von einer anderen Person registriert
tenant_required409enroll: Der Aufrufer gehört mehreren Mandanten an und hat keinen genannt; die Meldung listet sie auf
pool_exhausted503keine freie Adresse in VPN_CIDR
gateway_error500der Peer konnte nicht zu WireGuard hinzugefügt werden

RouteService (gRPC)

package claimward.routes.v1;

service RouteService {
  rpc Watch(WatchRequest) returns (stream RouteUpdate);
}
message WatchRequest { string public_key = 1; }
message RouteUpdate {
  repeated string allowed_ips = 1;
  repeated string dns = 2;
  uint64 serial = 3;   // increases on each change
}

Der Server lauscht auf GRPC_ADDR und kündigt GRPC_ENDPOINT in der Registrierungsantwort an. Das Bearer-Token wird in den Metadaten übertragen, authorization: Bearer <token>, und wie bei der HTTP-API geprüft. Watch sendet sofort die aktuelle Menge, dann jedes Mal eine neue, wenn sich die Routen des Mandanten ändern, für den Mandanten, in den sich das Gerät registriert hat, ermittelt über public_key, der vom Aufrufer registriert sein muss (andernfalls NOT_FOUND, PERMISSION_DENIED, sobald die Person diesen Mandanten verlassen hat).

Der Server stellt ihn mit TLS_CERT/TLS_KEY über TLS bereit. Die Clients (pkg/routeclient) sprechen TLS mit ihm, geprüft gegen die Stammzertifikate des Systems, außer zu einer Loopback-Adresse, und lehnen einen unverschlüsselten ab.

Erstellt am von tannevaled

Geändert am von tannevaled