Saltar al contenido
Protocolo de registro

Protocolo de registro

La API HTTP entre el helper de un dispositivo y claimward-vpn-server, sus errores, y el RouteService gRPC.

El contrato de comunicación entre los clientes y el servidor. Se define una sola vez en claimward-vpn-client/pkg/protocol (HTTP) y proto/routes/v1/routes.proto (gRPC), y ambos lados lo importan.

Cada petición autenticada envía el token bearer de la persona como Authorization: Bearer <token>, nunca en el cuerpo: un token de acceso OAuth de GitHub (AUTH_PROVIDER=github), un token de ID OIDC (oidc), o un token de acceso de go-authn (go-authn). Los cuerpos de las peticiones son JSON, como máximo 64 KiB, y los campos desconocidos se rechazan.

POST /api/v1/enroll

Petición:

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

tenant es opcional: sin él, el servidor usa el único inquilino de la persona. Los helpers envían os darwin, linux o windows y platform app-osx, app-linux o app-windows.

Respuesta 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 y dns son los del inquilino. dns se omite cuando el inquilino no tiene ninguno, y grpc_endpoint cuando el servidor no anuncia ningún RouteService (GRPC_ENDPOINT). El protocolo también tiene un mtu opcional, que el servidor (v0.2.0) nunca establece.

GET /api/v1/tenants

Respuesta 200: los inquilinos a los que puede conectarse quien hace la llamada.

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

POST /api/v1/heartbeat

{ "public_key": "<base64>" }

Respuesta 200: { "lease_expires_at": "..." }. 404 not_enrolled si la clave no está registrada por quien hace la llamada, 403 not_a_member en cuanto la persona ha dejado el inquilino de la sesión, 403 key_not_registered (go-authn) en cuanto el proveedor ya no lista la clave.

POST /api/v1/deregister

{ "public_key": "<base64>" }

Respuesta 204, también para una clave que no está registrada. 403 not_owner si la clave pertenece a otra persona.

GET /healthz

Respuesta 200: { "status": "ok" }, sin autenticación.

Errores

Las respuestas que no son 2xx contienen:

{ "error": "invalid_token", "message": "…" }
CódigoEstadoSignificado
missing_token / invalid_token401sin token bearer, o la verificación del proveedor ha fallado (incluida una lista de organizaciones o dominios permitidos)
bad_public_key / bad_request400entrada mal formada
key_not_registered403go-authn: esta persona no ha registrado la clave en el proveedor
not_a_member403el inquilino solicitado no es uno de los de quien hace la llamada, o ha dejado de serlo
not_owner403baja (deregister): la clave pertenece a otra persona
not_enrolled404heartbeat: quien hace la llamada no ha registrado esta clave
key_taken409registro: la clave está registrada por otra persona
tenant_required409registro: quien hace la llamada pertenece a varios inquilinos y no ha indicado ninguno; el mensaje los enumera
pool_exhausted503ninguna dirección libre en VPN_CIDR
gateway_error500no se ha podido añadir el par a WireGuard

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
}

El servidor escucha en GRPC_ADDR y anuncia GRPC_ENDPOINT en la respuesta de registro. El token bearer viaja en los metadatos, authorization: Bearer <token>, y se verifica igual que en la API HTTP. Watch envía el conjunto actual de inmediato y después uno nuevo cada vez que cambian las rutas del inquilino, para el inquilino en el que se registró el dispositivo, determinado por public_key, que debe estar registrada por quien hace la llamada (NOT_FOUND en caso contrario, PERMISSION_DENIED en cuanto la persona ha dejado ese inquilino).

El servidor lo sirve sobre TLS con TLS_CERT/TLS_KEY. Los clientes (pkg/routeclient) se comunican con él por TLS, verificado con las raíces del sistema, salvo hacia una dirección de loopback, y rechazan uno sin cifrar.

Creada el por tannevaled

Modificada el por tannevaled